diff options
712 files changed, 16468 insertions, 5642 deletions
diff --git a/.cirrus.yml b/.cirrus.yml index 4521866d1..7bc00dbb5 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -27,11 +27,17 @@ env: #### #### Cache-image names to test with ### - FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-7f4cd1f7" - PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-7f4cd1f7" - UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-84514d8b" + ACTIVE_CACHE_IMAGE_NAMES: >- + fedora-28-libpod-6318419153518592 + fedora-29-libpod-6318419153518592 + ubuntu-18-libpod-6318419153518592 + rhel-7-libpod-6318419153518592 + image-builder-image-1541772081 + FEDORA_CACHE_IMAGE_NAME: "fedora-29-libpod-6318419153518592" + PRIOR_FEDORA_CACHE_IMAGE_NAME: "fedora-28-libpod-6318419153518592" + UBUNTU_CACHE_IMAGE_NAME: "ubuntu-18-libpod-6318419153518592" + PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-6318419153518592" # RHEL_CACHE_IMAGE_NAME: "rhel-8-notready" - PRIOR_RHEL_CACHE_IMAGE_NAME: "rhel-7-libpod-7f4cd1f7" # CENTOS_CACHE_IMAGE_NAME: "centos-7-notready" #### @@ -61,6 +67,11 @@ env: RHEL_BASE_IMAGE: "rhel-guest-image-7-6-210-x86-64-qcow2-1548099756" #### + #### Default to NOT running in rootless-testing mode + #### + ROOTLESS_USER: "" + + #### #### Credentials and other secret-sauces, decrypted at runtime when authorized. #### # Freenode IRC credentials for posting status messages @@ -88,7 +99,7 @@ env: CIRRUS_TASK_ID CIRRUS_REPO_NAME CIRRUS_REPO_OWNER CIRRUS_REPO_FULL_NAME CIRRUS_REPO_CLONE_URL CIRRUS_SHELL CIRRUS_USER_COLLABORATOR CIRRUS_USER_PERMISSION CIRRUS_WORKING_DIR CIRRUS_HTTP_CACHE_HOST PACKER_BUILDS BUILT_IMAGE_SUFFIX - XDG_DATA_DIRS XDG_RUNTIME_DIR XDG_SESSION_ID + XDG_DATA_DIRS XDG_RUNTIME_DIR XDG_SESSION_ID ROOTLESS_USER # Every *_task runs in parallel in separate VMsd. The name prefix only for reference @@ -98,6 +109,7 @@ gating_task: env: CIRRUS_WORKING_DIR: "/usr/src/libpod" + GOSRC: "/go/src/github.com/containers/libpod" # Runs within Cirrus's "community cluster" container: @@ -105,23 +117,55 @@ gating_task: cpu: 4 memory: 12 + timeout_in: 20m + gate_script: + # N/B: entrypoint.sh resets $GOSRC (same as make clean) - '/usr/local/bin/entrypoint.sh validate' - '/usr/local/bin/entrypoint.sh lint' + - '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/test/test_dot_cirrus_yaml.py' + + # This task builds Podman with different buildtags to ensure the build does + # not break. It also verifies all sub-commands have man pages. + build_script: + - '/usr/local/bin/entrypoint.sh podman' + - 'cd $GOSRC && ./hack/podman-commands.sh' + # N/B: need 'clean' so some commited files are re-generated. + - '/usr/local/bin/entrypoint.sh clean podman-remote' + - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp"' + - '/usr/local/bin/entrypoint.sh podman-remote-darwin' + + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + + +# This task runs `make vendor` followed by ./hack/tree_status.sh to check +# whether the git tree is clean. The reasoning for that is to make sure +# that the vendor.conf, the code and the vendored packages in ./vendor are +# in sync at all times. +vendor_task: + + depends_on: + - "gating" + + env: + CIRRUS_WORKING_DIR: "/usr/src/libpod" + + # Runs within Cirrus's "community cluster" + container: + image: "quay.io/libpod/gate:latest" + cpu: 4 + memory: 12 + + timeout_in: 30m - # This task runs `make vendor` followed by ./hack/tree_status.sh to check - # whether the git tree is clean. The reasoning for that is to make sure - # that the vendor.conf, the code and the vendored packages in ./vendor are - # in sync at all times. vendor_script: - '/usr/local/bin/entrypoint.sh .install.vndr' - '/usr/local/bin/entrypoint.sh vendor' - 'cd /go/src/github.com/containers/libpod && ./hack/tree_status.sh' - # This task builds Podman with different buildtags to ensure the build does - # not break. - build_script: - - '/usr/local/bin/entrypoint.sh clean podman BUILDTAGS="exclude_graphdriver_devicemapper selinux seccomp"' + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' build_each_commit_task: @@ -138,15 +182,41 @@ build_each_commit_task: cpu: 2 memory: "4Gb" disk: 40 - matrix: - image_name: "${FEDORA_CACHE_IMAGE_NAME}" + image_name: "${FEDORA_CACHE_IMAGE_NAME}" timeout_in: 30m - script: - - $SCRIPT_BASE/setup_environment.sh - - git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH - - env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' + build_each_commit_script: + - 'git fetch --depth $CIRRUS_CLONE_DEPTH origin $CIRRUS_BASE_BRANCH' + - 'env GOPATH=/var/tmp/go/ make build-all-new-commits GIT_BASE_BRANCH=origin/$CIRRUS_BASE_BRANCH' + + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + + +# Update metadata on VM images referenced by this repository state +meta_task: + + depends_on: + - "gating" + + container: + image: "quay.io/libpod/imgts:latest" # see contrib/imgts + cpu: 1 + memory: 1 + + env: + # Space-separated list of images used by this repository state + IMGNAMES: "${ACTIVE_CACHE_IMAGE_NAMES}" + BUILDID: "${CIRRUS_BUILD_ID}" + REPOREF: "${CIRRUS_CHANGE_IN_REPO}" + GCPJSON: ENCRYPTED[950d9c64ad78f7b1f0c7e499b42dc058d2b23aa67e38b315e68f557f2aba0bf83068d4734f7b1e1bdd22deabe99629df] + GCPNAME: ENCRYPTED[b05d469a0dba8cb479cb00cc7c1f6747c91d17622fba260a986b976aa6c817d4077eacffd4613d6d5f23afc4084fab1d] + GCPPROJECT: ENCRYPTED[7c80e728e046b1c76147afd156a32c1c57d4a1ac1eab93b7e68e718c61ca8564fc61fef815952b8ae0a64e7034b8fe4f] + CIRRUS_CLONE_DEPTH: 1 # source not used + + script: '/usr/local/bin/entrypoint.sh' # This task does the unit and integration testing for every platform @@ -169,9 +239,9 @@ testing_task: image_name: "${FEDORA_CACHE_IMAGE_NAME}" image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" image_name: "${UBUNTU_CACHE_IMAGE_NAME}" - image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" - # TODO: tests fail + # TODO: Make these work (also optional_testing_task below) + # image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" # image_name: "${RHEL_CACHE_IMAGE_NAME}" # image_name: "${CENTOS_CACHE_IMAGE_NAME}" @@ -179,14 +249,43 @@ testing_task: # Every *_script runs in sequence, for each task. The name prefix is for # WebUI reference. The values may be strings... - setup_environment_script: $SCRIPT_BASE/setup_environment.sh + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' + unit_test_script: '$SCRIPT_BASE/unit_test.sh' + integration_test_script: '$SCRIPT_BASE/integration_test.sh' + + on_failure: + 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: + + depends_on: + - "gating" + - "build_each_commit" + + gce_instance: + image_project: "libpod-218412" + zone: "us-central1-a" # Required by Cirrus for the time being + cpu: 2 + memory: "4Gb" + disk: 200 + # A matrix could be used here, for now just one VM + image_name: "${FEDORA_CACHE_IMAGE_NAME}" + + env: + ROOTLESS_USER: "olympiclongjumpingwithjesus" + + timeout_in: 120m - # ...or lists of strings - unit_test_script: - - go version - - $SCRIPT_BASE/unit_test.sh + 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' # Because system tests are stored within the repository, it is sometimes @@ -206,15 +305,15 @@ optional_testing_task: image_name: "${FEDORA_CACHE_IMAGE_NAME}" image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}" image_name: "${UBUNTU_CACHE_IMAGE_NAME}" - image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" - # TODO: Make these work (also build_images_task below) + # TODO: Make these work (also testing_task above) # image_name: "${RHEL_CACHE_IMAGE_NAME}" + # image_name: "${PRIOR_RHEL_CACHE_IMAGE_NAME}" # image_name: "${CENTOS_CACHE_IMAGE_NAME}" timeout_in: 60m - setup_environment_script: $SCRIPT_BASE/setup_environment.sh - system_test_script: $SCRIPT_BASE/system_test.sh + setup_environment_script: '$SCRIPT_BASE/setup_environment.sh' + system_test_script: '$SCRIPT_BASE/system_test.sh' # Build new cache-images for future PR testing, but only after a PR merge. @@ -247,8 +346,8 @@ cache_images_task: scopes: - compute - devstorage.full_control - environment_script: $SCRIPT_BASE/setup_environment.sh - build_vm_images_script: $SCRIPT_BASE/build_vm_images.sh + environment_script: '$SCRIPT_BASE/setup_environment.sh' + build_vm_images_script: '$SCRIPT_BASE/build_vm_images.sh' # TODO,Continuous Delivery: Automatically open a libpod PR after using 'sed' to replace # the image_names with the new (just build) images. That will @@ -261,16 +360,21 @@ cache_images_task: # - modify_cirrus_yaml_image_names.sh # - commit_and_create_upstream_pr.sh + on_failure: + master_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_master_failure.sh' + # Post message to IRC if everything passed success_task: + only_if: $CIRRUS_BRANCH != 'master' + depends_on: # ignores any dependent task conditions - "gating" - - "vendor_check" + - "build_each_commit_task" - "testing" + - "rootless_testing_task" - "optional_testing" - - "cache_images" env: CIRRUS_WORKING_DIR: "/usr/src/libpod" @@ -280,4 +384,4 @@ success_task: cpu: 1 memory: 1 - success_script: $SCRIPT_BASE/success.sh + success_script: '$SCRIPT_BASE/success.sh' @@ -15,7 +15,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func ContainerExists(name: string) int](#ContainerExists) -[func ContainerInspectData(name: string) string](#ContainerInspectData) +[func ContainerInspectData(name: string, size: bool) string](#ContainerInspectData) [func ContainerRestore(name: string, keep: bool, tcpEstablished: bool) string](#ContainerRestore) @@ -43,6 +43,14 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func GetContainerStats(name: string) ContainerStats](#GetContainerStats) +[func GetContainerStatsWithHistory(previousStats: ContainerStats) ContainerStats](#GetContainerStatsWithHistory) + +[func GetContainersByContext(all: bool, latest: bool, args: []string) []string](#GetContainersByContext) + +[func GetContainersLogs(names: []string, follow: bool, latest: bool, since: string, tail: int, timestamps: bool) LogLine](#GetContainersLogs) + +[func GetEvents(filter: []string, since: string, until: string) Event](#GetEvents) + [func GetImage(id: string) Image](#GetImage) [func GetInfo() PodmanInfo](#GetInfo) @@ -129,6 +137,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func TagImage(name: string, tagged: string) string](#TagImage) +[func TopPod(pod: string, latest: bool, descriptors: []string) []string](#TopPod) + [func UnmountContainer(name: string, force: bool) ](#UnmountContainer) [func UnpauseContainer(name: string) string](#UnpauseContainer) @@ -141,7 +151,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func VolumesPrune() []string, []string](#VolumesPrune) -[func WaitContainer(name: string) int](#WaitContainer) +[func WaitContainer(name: string, interval: int) int](#WaitContainer) [type BuildInfo](#BuildInfo) @@ -163,6 +173,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type CreateResourceConfig](#CreateResourceConfig) +[type Event](#Event) + [type IDMap](#IDMap) [type IDMappingOptions](#IDMappingOptions) @@ -191,6 +203,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [type ListPodData](#ListPodData) +[type LogLine](#LogLine) + [type MoreResponse](#MoreResponse) [type NotImplemented](#NotImplemented) @@ -231,6 +245,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [error VolumeNotFound](#VolumeNotFound) +[error WantsMoreRequired](#WantsMoreRequired) + ## Methods ### <a name="BuildImage"></a>func BuildImage <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -284,7 +300,7 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.ContainerExists '{"name": ### <a name="ContainerInspectData"></a>func ContainerInspectData <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method ContainerInspectData(name: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> +method ContainerInspectData(name: [string](https://godoc.org/builtin#string), size: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div> ContainerInspectData returns a container's inspect data in string form. This call is for development of Podman only and generally should not be used. ### <a name="ContainerRestore"></a>func ContainerRestore @@ -460,6 +476,29 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.GetContainerStats '{"name } } ~~~ +### <a name="GetContainerStatsWithHistory"></a>func GetContainerStatsWithHistory +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetContainerStatsWithHistory(previousStats: [ContainerStats](#ContainerStats)) [ContainerStats](#ContainerStats)</div> +GetContainerStatsWithHistory takes a previous set of container statistics and uses libpod functions +to calculate the containers statistics based on current and previous measurements. +### <a name="GetContainersByContext"></a>func GetContainersByContext +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetContainersByContext(all: [bool](https://godoc.org/builtin#bool), latest: [bool](https://godoc.org/builtin#bool), args: [[]string](#[]string)) [[]string](#[]string)</div> +GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of +container names. The definition of latest container means the latest by creation date. In a multi- +user environment, results might differ from what you expect. +### <a name="GetContainersLogs"></a>func GetContainersLogs +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetContainersLogs(names: [[]string](#[]string), follow: [bool](https://godoc.org/builtin#bool), latest: [bool](https://godoc.org/builtin#bool), since: [string](https://godoc.org/builtin#string), tail: [int](https://godoc.org/builtin#int), timestamps: [bool](https://godoc.org/builtin#bool)) [LogLine](#LogLine)</div> + +### <a name="GetEvents"></a>func GetEvents +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method GetEvents(filter: [[]string](#[]string), since: [string](https://godoc.org/builtin#string), until: [string](https://godoc.org/builtin#string)) [Event](#Event)</div> +GetEvents returns known libpod events filtered by the options provided. ### <a name="GetImage"></a>func GetImage <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -690,7 +729,7 @@ See also [GetContainer](#GetContainer). method ListImages() [Image](#Image)</div> ListImages returns information about the images that are currently in storage. -See also [InspectImage](InspectImage). +See also [InspectImage](#InspectImage). ### <a name="ListPods"></a>func ListPods <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -952,6 +991,11 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.StopPod '{"name": "135d71 method TagImage(name: [string](https://godoc.org/builtin#string), tagged: [string](https://godoc.org/builtin#string)) [string](https://godoc.org/builtin#string)</div> TagImage takes the name or ID of an image in local storage as well as the desired tag name. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, the ID of the image is returned on success. +### <a name="TopPod"></a>func TopPod +<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> + +method TopPod(pod: [string](https://godoc.org/builtin#string), latest: [bool](https://godoc.org/builtin#bool), descriptors: [[]string](#[]string)) [[]string](#[]string)</div> + ### <a name="UnmountContainer"></a>func UnmountContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> @@ -1004,10 +1048,10 @@ VolumesPrune removes unused volumes on the host ### <a name="WaitContainer"></a>func WaitContainer <div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;"> -method WaitContainer(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)</div> -WaitContainer takes the name or ID of a container and waits until the container stops. Upon stopping, the return -code of the container is returned. If the container container cannot be found by ID or name, -a [ContainerNotFound](#ContainerNotFound) error is returned. +method WaitContainer(name: [string](https://godoc.org/builtin#string), interval: [int](https://godoc.org/builtin#int)) [int](https://godoc.org/builtin#int)</div> +WaitContainer takes the name or ID of a container and waits the given interval in milliseconds until the container +stops. Upon stopping, the return code of the container is returned. If the container container cannot be found by ID +or name, a [ContainerNotFound](#ContainerNotFound) error is returned. ## Types ### <a name="BuildInfo"></a>type BuildInfo @@ -1387,6 +1431,21 @@ pids_limit [int](https://godoc.org/builtin#int) shm_size [int](https://godoc.org/builtin#int) ulimit [[]string](#[]string) +### <a name="Event"></a>type Event + +Event describes a libpod struct + +id [string](https://godoc.org/builtin#string) + +image [string](https://godoc.org/builtin#string) + +name [string](https://godoc.org/builtin#string) + +status [string](https://godoc.org/builtin#string) + +time [string](https://godoc.org/builtin#string) + +type [string](https://godoc.org/builtin#string) ### <a name="IDMap"></a>type IDMap IDMap is used to describe user name spaces during container creation @@ -1586,6 +1645,19 @@ labels [map[string]](#map[string]) numberofcontainers [string](https://godoc.org/builtin#string) containersinfo [ListPodContainerInfo](#ListPodContainerInfo) +### <a name="LogLine"></a>type LogLine + + + +device [string](https://godoc.org/builtin#string) + +parseLogType [string](https://godoc.org/builtin#string) + +time [string](https://godoc.org/builtin#string) + +msg [string](https://godoc.org/builtin#string) + +cid [string](https://godoc.org/builtin#string) ### <a name="MoreResponse"></a>type MoreResponse MoreResponse is a struct for when responses from varlink requires longer output @@ -1746,3 +1818,6 @@ RuntimeErrors generally means a runtime could not be found or gotten. ### <a name="VolumeNotFound"></a>type VolumeNotFound VolumeNotFound means the volume could not be found by the name or ID in local storage. +### <a name="WantsMoreRequired"></a>type WantsMoreRequired + +The Podman endpoint requires that you use a streaming connection. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1b166fef..3778d6d7d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -329,10 +329,16 @@ author hold special privileges on the github repository. Others can be used by unintentionally. Instead, just write ``LGTM``, or spell it out. -* ``[skip ci]``: Within the HEAD commit will cause Cirrus CI to ***NOT*** execute - tests on the PR. This is useful in basically two cases: 1) You're still working - and don't want to waste resources. 2) You haven't modified any code that would - be exercised by the tests. For example, documentation updates (outside of code). +* ``/hold`` and ``/unhold``: Override the automatic handling of a request. Either + put it on hold (no handling) or remove the hold (normal handling). + +* ``[ci skip]``: [Adding `[ci skip]` within the HEAD commit](https://cirrus-ci.org/guide/writing-tasks/#conditional-task-execution) + will cause Cirrus CI to ***NOT*** execute tests for the PR or after merge. This + is useful in only one instance: Your changes are absolutely not exercised by + any test. For example, documentation changes. ***IMPORTANT NOTE*** **Other + automation may interpret the lack of test results as "PASSED" and unintentionall + merge a PR. Consider also using `/hold` in a comment, to add additional + protection.** [The complete list may be found on the command-help page.](https://prow.k8s.io/command-help) However, not all commands are implemented for this repository. If in doubt, ask a maintainer. diff --git a/Dockerfile.CentOS b/Dockerfile.CentOS index be4ae3eaf..605dc9df4 100644 --- a/Dockerfile.CentOS +++ b/Dockerfile.CentOS @@ -15,7 +15,7 @@ RUN yum -y install btrfs-progs-devel \ libassuan-devel \ libseccomp-devel \ libselinux-devel \ - skopeo-containers \ + containers-common \ runc \ make \ ostree-devel \ diff --git a/Dockerfile.Fedora b/Dockerfile.Fedora index aeee9c3cf..e38e2e056 100644 --- a/Dockerfile.Fedora +++ b/Dockerfile.Fedora @@ -16,7 +16,7 @@ RUN dnf -y install btrfs-progs-devel \ libassuan-devel \ libseccomp-devel \ libselinux-devel \ - skopeo-containers \ + containers-common \ runc \ make \ ostree-devel \ @@ -1,6 +1,6 @@ GO ?= go DESTDIR ?= / -EPOCH_TEST_COMMIT ?= 174e8997aa0d8fc648564a9ac2a79ab786e87362 +EPOCH_TEST_COMMIT ?= 1c45b42e9ff972d9645735118635e4186e6411f8 HEAD ?= HEAD CHANGELOG_BASE ?= HEAD~ CHANGELOG_TARGET ?= HEAD @@ -16,7 +16,6 @@ LIBEXECDIR ?= ${PREFIX}/libexec MANDIR ?= ${PREFIX}/share/man SHAREDIR_CONTAINERS ?= ${PREFIX}/share/containers ETCDIR ?= ${DESTDIR}/etc -ETCDIR_LIBPOD ?= ${ETCDIR}/crio TMPFILESDIR ?= ${PREFIX}/lib/tmpfiles.d SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system BUILDTAGS ?= seccomp $(shell hack/btrfs_tag.sh) $(shell hack/btrfs_installed_tag.sh) $(shell hack/ostree_tag.sh) $(shell hack/selinux_tag.sh) $(shell hack/apparmor_tag.sh) varlink exclude_graphdriver_devicemapper @@ -28,7 +27,7 @@ CONTAINER_RUNTIME := $(shell command -v podman 2> /dev/null || echo docker) OCI_RUNTIME ?= "" BASHINSTALLDIR=${PREFIX}/share/bash-completion/completions -OCIUMOUNTINSTALLDIR=$(PREFIX)/share/oci-umount/oci-umount.d +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 ) @@ -44,7 +43,7 @@ LIBSECCOMP_COMMIT := release-2.3 # Rarely if ever should integration tests take more than 50min, # caller may override in special circumstances if needed. -GINKGOTIMEOUT ?= -timeout=50m +GINKGOTIMEOUT ?= -timeout=90m # If GOPATH not specified, use one in the local directory ifeq ($(GOPATH),) @@ -177,7 +176,7 @@ localunit: test/goecho/goecho varlink_generate $(MAKE) -C contrib/cirrus/packer test ginkgo: - ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. + ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -nodes 3 test/e2e/. ginkgo-remote: ginkgo -v -tags "$(BUILDTAGS) remoteclient" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. @@ -242,14 +241,15 @@ install.man: docs install ${SELINUXOPT} -m 644 docs/links/*1 -t $(MANDIR)/man1 install.config: - install ${SELINUXOPT} -d -m 755 $(SHAREDIR_CONTAINERS) $(ETCDIR_LIBPOD) $(OCIUMOUNTINSTALLDIR) + install ${SELINUXOPT} -d -m 755 $(SHAREDIR_CONTAINERS) install ${SELINUXOPT} -m 644 libpod.conf $(SHAREDIR_CONTAINERS)/libpod.conf - install ${SELINUXOPT} -m 644 seccomp.json $(ETCDIR_LIBPOD)/seccomp.json - install ${SELINUXOPT} -m 644 crio-umount.conf $(OCIUMOUNTINSTALLDIR)/crio-umount.conf + install ${SELINUXOPT} -m 644 seccomp.json $(SHAREDIR_CONTAINERS)/seccomp.json install.completions: install ${SELINUXOPT} -d -m 755 ${BASHINSTALLDIR} install ${SELINUXOPT} -m 644 completions/bash/podman ${BASHINSTALLDIR} + install ${SELINUXOPT} -d -m 755 ${ZSHINSTALLDIR} + install ${SELINUXOPT} -m 644 completions/zsh/_podman ${ZSHINSTALLDIR} install.cni: install ${SELINUXOPT} -d -m 755 ${ETCDIR}/cni/net.d/ @@ -335,6 +335,7 @@ API.md: cmd/podman/varlink/io.podman.varlink validate.completions: completions/bash/podman . completions/bash/podman + if [ -x /bin/zsh ]; then /bin/zsh completions/zsh/_podman; fi validate: gofmt .gitvalidation validate.completions @@ -12,7 +12,7 @@ popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)` At a high level, the scope of libpod and podman is the following: -* Support multiple image formats including the existing Docker/OCI image formats. +* Support multiple image formats including the OCI and Docker image formats. * Support for multiple means to download images including trust & image verification. * Container image management (managing image layers, overlay filesystems, etc). * Full management of container lifecycle diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0bacad0d7..65a3f5eea 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,45 @@ # Release Notes +## 1.1.2 +### Bugfixes +- Fixed a bug where the `podman image list`, `podman image rm`, and `podman container list` had broken global storage options +- Fixed a bug where the `--label` option to `podman create` and `podman run` was missing the `-l` alias +- Fixed a bug where running Podman with the `--config` flag would not set an appropriate default value for `tmp_dir` ([#2408](https://github.com/containers/libpod/issues/2408)) +- Fixed a bug where the `podman logs` command with the `--timestamps` flag produced unreadable output ([#2500](https://github.com/containers/libpod/issues/2500)) +- Fixed a bug where the `podman cp` command would automatically extract `.tar` files copied into the container ([#2509](https://github.com/containers/libpod/issues/2509)) + +### Misc +- The `podman container stop` command is now usable with the Podman remote client + +## 1.1.1 +### Bugfixes +- Fixed a bug where `podman container restore` was erroneously available as `podman restore` ([#2191](https://github.com/containers/libpod/issues/2191)) +- Fixed a bug where the `volume_path` option in `libpod.conf` was not being respected +- Fixed a bug where Podman failed to build when the `varlink` tag was not present ([#2459](https://github.com/containers/libpod/issues/2459)) +- Fixed a bug where the `podman image load` command was listed twice in help text +- Fixed a bug where the `podman image sign` command was also listed as `podman sign` +- Fixed a bug where the `podman image list` command incorrectly had an `image` alias +- Fixed a bug where the `podman images` command incorrectly had `ls` and `list` aliases +- Fixed a bug where the `podman image rm` command was being displayed as `podman image rmi` +- Fixed a bug where the `podman create` command would attempt to parse arguments meant for the container +- Fixed a bug where the combination of FIPS mode and user namespaces resulted in permissions errors +- Fixed a bug where the `--time` alias for `--timeout` for the `podman restart` and `podman stop` commands did not function +- Fixed a bug where the default stop timeout for newly-created containers was being set to 0 seconds (resulting in an immediate SIGKILL on running `podman stop`) +- Fixed a bug where the output format of `podman port` was incorrect, printing full container ID instead of truncated ID +- Fixed a bug where the `podman container list` command did not exist +- Fixed a bug where `podman build` could not build a container from images tagged locally that did not exist in a registry ([#2469](https://github.com/containers/libpod/issues/2469)) +- Fixed a bug where some Podman commands that accept no arguments would not error when provided arguments +- Fixed a bug where `podman play kube` could not handle cases where a pod and a container shared a name + +### Misc +- Usage text for many commands was greatly improved +- Major cleanups were made to Podman manpages, ensuring that command lists are accurate +- Greatly improved debugging output when the `newuidmap` and `newgidmap` binaries fail when using rootless Podman +- The `-s` alias for the global `--storage-driver` option has been removed +- The `podman container refresh` command has been deprecated, as its intended use case is no longer relevant. The command has been hidden and manpages deleted. It will be removed in a future release +- The `podman container runlabel` command will now pull images not available locally even without the `--pull` option. The `--pull` option has been deprecated +- The `podman container checkpoint` and `podman container restore` commands are now only available on OCI runtimes where they are supported (e.g. `runc`) + ## 1.1.0 ### Features - Added `--latest` and `--all` flags to `podman mount` and `podman umount` @@ -59,7 +99,7 @@ - Updated Buildah to v1.7, picking up a number of bugfixes - Updated containers/image library to v1.5, picking up a number of bugfixes and performance improvements to pushing images - Updated containers/storage library to v1.10, picking up a number of bugfixes -- Work on the remote Podman client for interacting with Podman remotely over Varlink is progressing steadily, and many image and pod commands are supported +- Work on the remote Podman client for interacting with Podman remotely over Varlink is progressing steadily, and many image and pod commands are supported - please see the [Readme](https://github.com/containers/libpod/blob/master/remote_client.md) for details - Added path masking to mounts with the `:z` and `:Z` options, preventing users from accidentally performing an SELinux relabel of their entire home directory - The `podman container runlabel` command will not pull an image if it does not contain the requested label - Many commands' usage information now includes examples diff --git a/changelog.txt b/changelog.txt index 803aad796..7db579f3a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,63 @@ +- Changelog for v1.1.2 (2019-03-04) + * Fix #2521 + * Update release notes for v1.1.2 + * Change timestamp format for podman logs + * Don't extract tar file in podman cp + * runtime: fill a proper default tmpdir when --config is used + * Add additional defense against 0-length log segfaults + * When logging with timestamps, append only until newline + * Ensure that each log line is newline-terminated + * A few more usage-message tweaks + * Add missing short flag -l for run/create + * Fix aliased commands to actually work + * Support podman-remote stop container(s) + * Add tests to make sure podman container and podman image commands work + * Bump gitvalidation epoch + * Bump to v1.2.0-dev + +- Changelog for v1.1.1 (2019-03-01) + * Update release notes for v1.1.1 + * Pull image for runlabel if not local + * Fix SystemExec completion race + * Fix link inconsistencies in man pages + * Verify that used OCI runtime supports checkpoint + * Should be defaulting to pull not pull-always + * podman-commands script: refactor + * Move Alias lines to descriptions of commands + * Fix usage messages for podman image list, rm + * Fix -s to --storage-driver in baseline test + * No podman container ps command exists + * Allow Exec API user to override streams + * fix up a number of misplace commands + * rootless, new[ug]idmap: on failure add output + * [ci skip] Critical note about merge bot + * podman port fix output + * Fix ignored --time argument to podman restart + * secrets: fix fips-mode with user namespaces + * Fix four errors tagged by Cobra macro debugging + * Clean up man pages to match commands + * Add debugging for errors to Cobra compatibility macros + * Command-line input validation: reject unused args + * Fix ignored --stop-timeout flag to 'podman create' + * fixup! Incorporate review feedback + * fixup! missed some more: + * fixup! Correction to 'checkpoint' + * Followup to #2456: update examples, add trust + * podman create: disable interspersed opts + * fix up a number of misplace commands + * Add a task to Cirrus gating to build w/o Varlink + * Skip checkpoint/restore tests on Fedora for now + * Fix build for non-Varlink-tagged Podman + * Remove restore as podman subcommand + * Better usage synopses for subcommands + * Bump gitvalidation epoch + * Bump to v1.2.0-dev + * Centralize setting default volume path + * Ensure volume path is set appropriately by default + * Move all storage configuration defaults into libpod + * rename pod when we have a name collision with a container + * podman remote-client readme + - Changelog for v1.1.0 (2019-02-26) * Vendor in latest buildah 1.7.1 * volume: do not create a volume if there is a bind diff --git a/cmd/podman/attach.go b/cmd/podman/attach.go index a22aa92a1..86e89cfd7 100644 --- a/cmd/podman/attach.go +++ b/cmd/podman/attach.go @@ -30,11 +30,12 @@ var ( func init() { attachCommand.Command = _attachCommand + attachCommand.SetHelpTemplate(HelpTemplate()) attachCommand.SetUsageTemplate(UsageTemplate()) flags := attachCommand.Flags() flags.StringVar(&attachCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.BoolVar(&attachCommand.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false") - flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true)") + flags.BoolVar(&attachCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process") flags.BoolVarP(&attachCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") markFlagHiddenForRemoteClient("latest", flags) } diff --git a/cmd/podman/build.go b/cmd/podman/build.go index cfeabfb4e..f0a67791a 100644 --- a/cmd/podman/build.go +++ b/cmd/podman/build.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "path/filepath" "strings" @@ -11,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/pkg/adapter" "github.com/docker/go-units" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -18,8 +20,7 @@ import ( var ( buildCommand cliconfig.BuildValues - buildDescription = "Builds an OCI or Docker image using instructions from one\n" + - "or more Dockerfiles and a specified build context directory." + buildDescription = "Builds an OCI or Docker image using instructions from one or more Dockerfiles and a specified build context directory." layerValues buildahcli.LayerResults budFlagsValues buildahcli.BudResults fromAndBudValues buildahcli.FromAndBudResults @@ -48,9 +49,10 @@ var ( func init() { buildCommand.Command = _buildCommand + buildCommand.SetHelpTemplate(HelpTemplate()) buildCommand.SetUsageTemplate(UsageTemplate()) flags := buildCommand.Flags() - flags.SetInterspersed(false) + flags.SetInterspersed(true) budFlags := buildahcli.GetBudFlags(&budFlagsValues) flag := budFlags.Lookup("pull") @@ -83,6 +85,26 @@ func getDockerfiles(files []string) []string { return dockerfiles } +func getNsValues(c *cliconfig.BuildValues) ([]buildah.NamespaceOption, error) { + var ret []buildah.NamespaceOption + if c.Network != "" { + if c.Network == "host" { + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + Host: true, + }) + } else if c.Network[0] == '/' { + ret = append(ret, buildah.NamespaceOption{ + Name: string(specs.NetworkNamespace), + Path: c.Network, + }) + } else { + return nil, fmt.Errorf("unsupported configuration network=%s", c.Network) + } + } + return ret, nil +} + func buildCmd(c *cliconfig.BuildValues) error { // The following was taken directly from containers/buildah/cmd/bud.go // TODO Find a away to vendor more of this in rather than copy from bud @@ -227,6 +249,11 @@ func buildCmd(c *cliconfig.BuildValues) error { } } + nsValues, err := getNsValues(c) + if err != nil { + return err + } + buildOpts := buildah.CommonBuildOptions{ AddHost: c.AddHost, CgroupParent: c.CgroupParent, @@ -257,6 +284,7 @@ func buildCmd(c *cliconfig.BuildValues) error { IIDFile: c.Iidfile, Labels: c.Label, Layers: layers, + NamespaceOptions: nsValues, NoCache: c.NoCache, Out: stdout, Output: output, diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index 367065766..dbf72c2cd 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -40,6 +40,7 @@ var ( func init() { checkpointCommand.Command = _checkpointCommand + checkpointCommand.SetHelpTemplate(HelpTemplate()) checkpointCommand.SetUsageTemplate(UsageTemplate()) flags := checkpointCommand.Flags() diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index fbbd337a7..17e637da1 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -37,6 +37,7 @@ var ( func init() { cleanupCommand.Command = _cleanupCommand + cleanupCommand.SetHelpTemplate(HelpTemplate()) cleanupCommand.SetUsageTemplate(UsageTemplate()) flags := cleanupCommand.Flags() diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index a9032202f..884bd7fdb 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -25,6 +25,7 @@ type MainFlags struct { StorageOpts []string Syslog bool Trace bool + NetworkCmdPath string Config string CpuProfile string @@ -52,10 +53,24 @@ type ImagesValues struct { Sort string } +type EventValues struct { + PodmanCommand + Filter []string + Format string + Since string + Stream bool + Until string +} + type TagValues struct { PodmanCommand } +type TreeValues struct { + PodmanCommand + WhatRequires bool +} + type WaitValues struct { PodmanCommand Interval uint @@ -100,6 +115,7 @@ type ExecValues struct { User string Latest bool Workdir string + PreserveFDs int } type ImageExistsValues struct { @@ -216,6 +232,10 @@ type PauseValues struct { All bool } +type HealthCheckValues struct { + PodmanCommand +} + type KubePlayValues struct { PodmanCommand Authfile string @@ -401,15 +421,15 @@ type RmiValues struct { type RunlabelValues struct { PodmanCommand Authfile string - Display bool CertDir string Creds string + Display bool Name string Opt1 string Opt2 string Opt3 string Quiet bool - Pull bool + Replace bool SignaturePolicy string TlsVerify bool } @@ -552,3 +572,9 @@ type SystemPruneValues struct { type SystemRenumberValues struct { PodmanCommand } + +type SystemDfValues struct { + PodmanCommand + Verbose bool + Format string +} diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go index b5ca1be9c..49ab3d827 100644 --- a/cmd/podman/cliconfig/create.go +++ b/cmd/podman/cliconfig/create.go @@ -23,4 +23,5 @@ type BuildValues struct { type CpValues struct { PodmanCommand + Extract bool } diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 2f9a9cfe2..875b2aec8 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -21,7 +21,6 @@ func getMainCommands() []*cobra.Command { &_psCommand, _loginCommand, _logoutCommand, - _logsCommand, _mountCommand, _pauseCommand, _portCommand, @@ -32,11 +31,9 @@ func getMainCommands() []*cobra.Command { _searchCommand, _startCommand, _statsCommand, - _stopCommand, _topCommand, _umountCommand, _unpauseCommand, - _waitCommand, } if len(_varlinkCommand.Use) > 0 { @@ -55,9 +52,6 @@ func getImageSubCommands() []*cobra.Command { // Commands that the local client implements func getContainerSubCommands() []*cobra.Command { - var _listSubCommand = _psCommand - _listSubCommand.Use = "list" - return []*cobra.Command{ _attachCommand, _checkpointCommand, @@ -68,8 +62,6 @@ func getContainerSubCommands() []*cobra.Command { _execCommand, _exportCommand, _killCommand, - &_listSubCommand, - _logsCommand, _mountCommand, _pauseCommand, _portCommand, @@ -90,14 +82,6 @@ func getContainerSubCommands() []*cobra.Command { } } -// Commands that the local client implements -func getPodSubCommands() []*cobra.Command { - return []*cobra.Command{ - _podStatsCommand, - _podTopCommand, - } -} - func getGenerateSubCommands() []*cobra.Command { return []*cobra.Command{ _containerKubeCommand, @@ -124,5 +108,13 @@ func getSystemSubCommands() []*cobra.Command { return []*cobra.Command{ _pruneSystemCommand, _renumberCommand, + _dfSystemCommand, + } +} + +// Commands that the local client implements +func getHealtcheckSubCommands() []*cobra.Command { + return []*cobra.Command{ + _healthcheckrunCommand, } } diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go index 081043b25..9b09e7dbc 100644 --- a/cmd/podman/commands_remoteclient.go +++ b/cmd/podman/commands_remoteclient.go @@ -29,11 +29,6 @@ func getContainerSubCommands() []*cobra.Command { } // commands that only the remoteclient implements -func getPodSubCommands() []*cobra.Command { - return []*cobra.Command{} -} - -// commands that only the remoteclient implements func getGenerateSubCommands() []*cobra.Command { return []*cobra.Command{} } @@ -52,3 +47,8 @@ func getTrustSubCommands() []*cobra.Command { func getSystemSubCommands() []*cobra.Command { return []*cobra.Command{} } + +// Commands that the remoteclient implements +func getHealtcheckSubCommands() []*cobra.Command { + return []*cobra.Command{} +} diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 43c54c320..584ab6880 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -19,10 +19,7 @@ import ( var ( commitCommand cliconfig.CommitValues - commitDescription = `Create an image from a container's changes. - Optionally tag the image created, set the author with the --author flag, - set the commit message with the --message flag, - and make changes to the instructions with the --change flag.` + commitDescription = `Create an image from a container's changes. Optionally tag the image created, set the author with the --author flag, set the commit message with the --message flag, and make changes to the instructions with the --change flag.` _commitCommand = &cobra.Command{ Use: "commit [flags] CONTAINER IMAGE", @@ -41,6 +38,7 @@ var ( func init() { commitCommand.Command = _commitCommand + commitCommand.SetHelpTemplate(HelpTemplate()) commitCommand.SetUsageTemplate(UsageTemplate()) flags := commitCommand.Flags() flags.StringSliceVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | "))) diff --git a/cmd/podman/common.go b/cmd/podman/common.go index e980e10f9..771738302 100644 --- a/cmd/podman/common.go +++ b/cmd/podman/common.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "github.com/spf13/cobra" "os" "strings" @@ -14,6 +13,7 @@ import ( "github.com/containers/storage" "github.com/fatih/camelcase" "github.com/pkg/errors" + "github.com/spf13/cobra" ) var ( @@ -59,6 +59,24 @@ func checkAllAndLatest(c *cobra.Command, args []string, ignoreArgLen bool) error return nil } +// noSubArgs checks that there are no further positional parameters +func noSubArgs(c *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.Errorf("`%s` takes no arguments", c.CommandPath()) + } + return nil +} + +func commandRunE() func(*cobra.Command, []string) error { + return func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + return errors.Errorf("unrecognized command `%s %s`\nTry '%s --help' for more information.", cmd.CommandPath(), args[0], cmd.CommandPath()) + } else { + return errors.Errorf("missing command '%s COMMAND'\nTry '%s --help' for more information.", cmd.CommandPath(), cmd.CommandPath()) + } + } +} + // getAllOrLatestContainers tries to return the correct list of containers // depending if --all, --latest or <container-id> is used. // It requires the Context (c) and the Runtime (runtime). As different @@ -208,7 +226,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.String( "detach-keys", "", - "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", + "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`", ) createFlags.StringSlice( "device", []string{}, @@ -246,7 +264,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "entrypoint", "", "Overwrite the default ENTRYPOINT of the image", ) - createFlags.StringSliceP( + createFlags.StringArrayP( "env", "e", []string{}, "Set environment variables in container", ) @@ -269,14 +287,33 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { createFlags.Bool( "help", false, "", ) - + createFlags.String( + "healthcheck-command", "", + "set a healthcheck command for the container ('none' disables the existing healthcheck)", + ) + createFlags.String( + "healthcheck-interval", "30s", + "set an interval for the healthchecks (a value of disable results in no automatic timer setup)", + ) + createFlags.Uint( + "healthcheck-retries", 3, + "the number of retries allowed before a healthcheck is considered to be unhealthy", + ) + createFlags.String( + "healthcheck-start-period", "0s", + "the initialization time needed for a container to bootstrap", + ) + createFlags.String( + "healthcheck-timeout", "30s", + "the maximum time allowed to complete the healthcheck before an interval is considered failed", + ) createFlags.StringP( "hostname", "h", "", "Set container hostname", ) createFlags.String( "image-volume", "bind", - "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore' (default 'bind')", + "Tells podman how to handle the builtin image volumes. The options are: 'bind', 'tmpfs', or 'ignore'", ) createFlags.Bool( "init", false, @@ -303,8 +340,8 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { "kernel-memory", "", "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)", ) - createFlags.StringSlice( - "label", []string{}, + createFlags.StringArrayP( + "label", "l", []string{}, "Set metadata on container (default [])", ) createFlags.StringSlice( @@ -337,7 +374,7 @@ func getCreateFlags(c *cliconfig.PodmanCommand) { ) createFlags.Int64( "memory-swappiness", -1, - "Tune container memory swappiness (0 to 100) (default -1)", + "Tune container memory swappiness (0 to 100, or -1 for system default)", ) createFlags.String( "name", "", @@ -513,11 +550,23 @@ func scrubServer(server string) string { return strings.TrimPrefix(server, "http://") } +// HelpTemplate returns the help template for podman commands +// This uses the short and long options. +// command should not use this. +func HelpTemplate() string { + return `{{.Short}} + +Description: + {{.Long}} + +{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}` +} + // UsageTemplate returns the usage template for podman commands // This blocks the desplaying of the global options. The main podman // command should not use this. func UsageTemplate() string { - return `Usage:{{if .Runnable}} + return `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 338bb005c..2e9cedbaa 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -1,27 +1,69 @@ package main import ( + "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/spf13/cobra" ) -var containerDescription = "Manage containers" -var containerCommand = cliconfig.PodmanCommand{ - Command: &cobra.Command{ - Use: "container", - Short: "Manage Containers", - Long: containerDescription, - TraverseChildren: true, - }, -} +var ( + containerDescription = "Manage containers" + containerCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "container", + Short: "Manage Containers", + Long: containerDescription, + TraverseChildren: true, + RunE: commandRunE(), + }, + } -// Commands that are universally implemented. -var containerCommands = []*cobra.Command{ - _containerExistsCommand, - _inspectCommand, -} + contInspectSubCommand cliconfig.InspectValues + _contInspectSubCommand = &cobra.Command{ + Use: strings.Replace(_inspectCommand.Use, "| IMAGE", "", 1), + Short: "Display the configuration of a container", + Long: `Displays the low-level information on a container identified by name or ID.`, + RunE: func(cmd *cobra.Command, args []string) error { + contInspectSubCommand.InputArgs = args + contInspectSubCommand.GlobalFlags = MainGlobalOpts + return inspectCmd(&contInspectSubCommand) + }, + Example: `podman container inspect myCtr + podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, + } + + listSubCommand cliconfig.PsValues + _listSubCommand = &cobra.Command{ + Use: strings.Replace(_psCommand.Use, "ps", "list", 1), + Args: noSubArgs, + Short: _psCommand.Short, + Long: _psCommand.Long, + Aliases: []string{"ls"}, + RunE: func(cmd *cobra.Command, args []string) error { + listSubCommand.InputArgs = args + listSubCommand.GlobalFlags = MainGlobalOpts + return psCmd(&listSubCommand) + }, + Example: strings.Replace(_psCommand.Example, "podman ps", "podman container list", -1), + } + + // Commands that are universally implemented. + containerCommands = []*cobra.Command{ + _containerExistsCommand, + _contInspectSubCommand, + _listSubCommand, + _logsCommand, + } +) func init() { + contInspectSubCommand.Command = _contInspectSubCommand + inspectInit(&contInspectSubCommand) + + listSubCommand.Command = _listSubCommand + psInit(&listSubCommand) + containerCommand.AddCommand(containerCommands...) containerCommand.AddCommand(getContainerSubCommands()...) containerCommand.SetUsageTemplate(UsageTemplate()) diff --git a/cmd/podman/containers_prune.go b/cmd/podman/containers_prune.go index 6e4960429..39be70c5b 100644 --- a/cmd/podman/containers_prune.go +++ b/cmd/podman/containers_prune.go @@ -21,6 +21,7 @@ var ( ` _pruneContainersCommand = &cobra.Command{ Use: "prune", + Args: noSubArgs, Short: "Remove all stopped containers", Long: pruneContainersDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -33,6 +34,7 @@ var ( func init() { pruneContainersCommand.Command = _pruneContainersCommand + pruneContainersCommand.SetHelpTemplate(HelpTemplate()) pruneContainersCommand.SetUsageTemplate(UsageTemplate()) flags := pruneContainersCommand.Flags() flags.BoolVarP(&pruneContainersCommand.Force, "force", "f", false, "Force removal of a running container. The default is false") diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go index 30b6d75d2..6223676ac 100644 --- a/cmd/podman/cp.go +++ b/cmd/podman/cp.go @@ -27,8 +27,11 @@ import ( var ( cpCommand cliconfig.CpValues - cpDescription = "Copy files/folders between a container and the local filesystem" - _cpCommand = &cobra.Command{ + cpDescription = `Command copies the contents of SRC_PATH to the DEST_PATH. + + You can copy from the container's file system to the local machine or the reverse, from the local filesystem to the container. If "-" is specified for either the SRC_PATH or DEST_PATH, you can also stream a tar archive from STDIN or to STDOUT. The CONTAINER can be a running or stopped container. The SRC_PATH or DEST_PATH can be a file or directory. +` + _cpCommand = &cobra.Command{ Use: "cp [flags] SRC_PATH DEST_PATH", Short: "Copy files/folders between a container and the local filesystem", Long: cpDescription, @@ -43,6 +46,10 @@ var ( func init() { cpCommand.Command = _cpCommand + flags := cpCommand.Flags() + flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.") + cpCommand.SetHelpTemplate(HelpTemplate()) + cpCommand.SetUsageTemplate(UsageTemplate()) rootCmd.AddCommand(cpCommand.Command) } @@ -61,10 +68,11 @@ func cpCmd(c *cliconfig.CpValues) error { } defer runtime.Shutdown(false) - return copyBetweenHostAndContainer(runtime, args[0], args[1]) + extract := c.Flag("extract").Changed + return copyBetweenHostAndContainer(runtime, args[0], args[1], extract) } -func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string) error { +func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool) error { srcCtr, srcPath := parsePath(runtime, src) destCtr, destPath := parsePath(runtime, dest) @@ -166,7 +174,7 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin var lastError error for _, src := range glob { - err := copy(src, destPath, dest, idMappingOpts, &containerOwner) + err := copy(src, destPath, dest, idMappingOpts, &containerOwner, extract) if lastError != nil { logrus.Error(lastError) } @@ -219,7 +227,7 @@ func getPathInfo(path string) (string, os.FileInfo, error) { return path, srcfi, nil } -func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair) error { +func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract bool) error { srcPath, err := filepath.EvalSymlinks(src) if err != nil { return errors.Wrapf(err, "error evaluating symlinks %q", srcPath) @@ -263,17 +271,20 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch if destfi != nil && destfi.IsDir() { destPath = filepath.Join(destPath, filepath.Base(srcPath)) } - // Copy the file, preserving attributes. - logrus.Debugf("copying %q to %q", srcPath, destPath) - if err = copyFileWithTar(srcPath, destPath); err != nil { - return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) + } + + if extract { + // We're extracting an archive into the destination directory. + logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) + if err = untarPath(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) } return nil } - // We're extracting an archive into the destination directory. - logrus.Debugf("extracting contents of %q into %q", srcPath, destPath) - if err = untarPath(srcPath, destPath); err != nil { - return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath) + // Copy the file, preserving attributes. + logrus.Debugf("copying %q to %q", srcPath, destPath) + if err = copyFileWithTar(srcPath, destPath); err != nil { + return errors.Wrapf(err, "error copying %q to %q", srcPath, destPath) } return nil } diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 95cb732d9..bceb606f6 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -1,46 +1,23 @@ package main import ( - "context" - "encoding/json" "fmt" - "io" - "io/ioutil" "os" - "path/filepath" - "strconv" - "strings" - "syscall" "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" - ann "github.com/containers/libpod/pkg/annotations" - "github.com/containers/libpod/pkg/inspect" - ns "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/docker/go-connections/nat" - "github.com/docker/go-units" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/selinux/go-selinux/label" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( createCommand cliconfig.CreateValues - createDescription = "Creates a new container from the given image or" + - " storage and prepares it for running the specified command. The" + - " container ID is then printed to stdout. You can then start it at" + - " any time with the podman start <container_id> command. The container" + - " will be created with the initial state 'created'." + createDescription = `Creates a new container from the given image or storage and prepares it for running the specified command. + + The container ID is then printed to stdout. You can then start it at any time with the podman start <container_id> command. The container will be created with the initial state 'created'.` _createCommand = &cobra.Command{ Use: "create [flags] IMAGE [COMMAND [ARG...]]", Short: "Create but do not start a container", @@ -54,15 +31,11 @@ var ( podman create --annotation HELLO=WORLD alpine ls podman create -t -i --name myctr alpine ls`, } - - defaultEnvVariables = map[string]string{ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "TERM": "xterm", - } ) func init() { createCommand.PodmanCommand.Command = _createCommand + createCommand.SetHelpTemplate(HelpTemplate()) createCommand.SetUsageTemplate(UsageTemplate()) getCreateFlags(&createCommand.PodmanCommand) @@ -91,7 +64,7 @@ func createCmd(c *cliconfig.CreateValues) error { } defer runtime.Shutdown(false) - ctr, _, err := createContainer(&c.PodmanCommand, runtime) + ctr, _, err := shared.CreateContainer(getContext(), &c.PodmanCommand, runtime) if err != nil { return err } @@ -115,795 +88,3 @@ func createInit(c *cliconfig.PodmanCommand) error { return nil } - -func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "createContainer") - defer span.Finish() - } - - rtc := runtime.GetConfig() - ctx := getContext() - rootfs := "" - if c.Bool("rootfs") { - rootfs = c.InputArgs[0] - } - - var err error - var cidFile *os.File - if c.IsSet("cidfile") && os.Geteuid() == 0 { - cidFile, err = libpod.OpenExclusiveFile(c.String("cidfile")) - if err != nil && os.IsExist(err) { - return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile")) - } - if err != nil { - return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile")) - } - defer cidFile.Close() - defer cidFile.Sync() - } - - imageName := "" - var data *inspect.ImageData = nil - - if rootfs == "" && !rootless.SkipStorageSetup() { - var writer io.Writer - if !c.Bool("quiet") { - writer = os.Stderr - } - - newImage, err := runtime.ImageRuntime().New(ctx, c.InputArgs[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) - if err != nil { - return nil, nil, err - } - data, err = newImage.Inspect(ctx) - names := newImage.Names() - if len(names) > 0 { - imageName = names[0] - } else { - imageName = newImage.ID() - } - } - createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data) - if err != nil { - return nil, nil, err - } - - ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, nil) - if err != nil { - return nil, nil, err - } - if cidFile != nil { - _, err = cidFile.WriteString(ctr.ID()) - if err != nil { - logrus.Error(err) - } - - } - - logrus.Debugf("New container created %q", ctr.ID()) - return ctr, createConfig, nil -} - -func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { - var ( - labelOpts []string - ) - - if config.PidMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.PidMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) - } - secopts, err := label.DupSecOpt(ctr.ProcessLabel()) - if err != nil { - return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) - } - labelOpts = append(labelOpts, secopts...) - } - - if config.IpcMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.IpcMode.IsContainer() { - ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) - } - secopts, err := label.DupSecOpt(ctr.ProcessLabel()) - if err != nil { - return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) - } - labelOpts = append(labelOpts, secopts...) - } - - for _, opt := range securityOpts { - if opt == "no-new-privileges" { - config.NoNewPrivs = true - } else { - con := strings.SplitN(opt, "=", 2) - if len(con) != 2 { - return fmt.Errorf("Invalid --security-opt 1: %q", opt) - } - - switch con[0] { - case "label": - labelOpts = append(labelOpts, con[1]) - case "apparmor": - config.ApparmorProfile = con[1] - case "seccomp": - config.SeccompProfilePath = con[1] - default: - return fmt.Errorf("Invalid --security-opt 2: %q", opt) - } - } - } - - if config.SeccompProfilePath == "" { - if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { - config.SeccompProfilePath = libpod.SeccompOverridePath - } else { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompOverridePath) - } - if _, err := os.Stat(libpod.SeccompDefaultPath); err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompDefaultPath) - } - } else { - config.SeccompProfilePath = libpod.SeccompDefaultPath - } - } - } - config.LabelOpts = labelOpts - return nil -} - -// isPortInPortBindings determines if an exposed host port is in user -// provided ports -func isPortInPortBindings(pb map[nat.Port][]nat.PortBinding, port nat.Port) bool { - var hostPorts []string - for _, i := range pb { - hostPorts = append(hostPorts, i[0].HostPort) - } - return util.StringInSlice(port.Port(), hostPorts) -} - -// isPortInImagePorts determines if an exposed host port was given to us by metadata -// in the image itself -func isPortInImagePorts(exposedPorts map[string]struct{}, port string) bool { - for i := range exposedPorts { - fields := strings.Split(i, "/") - if port == fields[0] { - return true - } - } - return false -} - -func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string { - entrypoint := []string{} - if c.IsSet("entrypoint") { - // Force entrypoint to "" - if c.String("entrypoint") == "" { - return entrypoint - } - // Check if entrypoint specified is json - if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil { - return entrypoint - } - // Return entrypoint as a single command - return []string{c.String("entrypoint")} - } - if data != nil { - return data.Config.Entrypoint - } - return entrypoint -} - -func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { - pod, err := runtime.LookupPod(podName) - if err != nil { - return namespaces, err - } - podInfraID, err := pod.InfraContainerID() - if err != nil { - return namespaces, err - } - if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { - namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { - namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { - namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) { - namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID) - } - if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) { - namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID) - } - return namespaces, nil -} - -// 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) { - var ( - inputCommand, command []string - memoryLimit, memoryReservation, memorySwap, memoryKernel int64 - blkioWeight uint16 - namespaces map[string]string - ) - if c.IsSet("restart") { - return nil, errors.Errorf("--restart option is not supported.\nUse systemd unit files for restarting containers") - } - - idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) - if err != nil { - return nil, err - } - - if c.String("mac-address") != "" { - return nil, errors.Errorf("--mac-address option not currently supported") - } - - imageID := "" - - inputCommand = c.InputArgs[1:] - if data != nil { - imageID = data.ID - } - - rootfs := "" - if c.Bool("rootfs") { - rootfs = c.InputArgs[0] - } - - sysctl, err := validateSysctl(c.StringSlice("sysctl")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for sysctl") - } - - if c.String("memory") != "" { - memoryLimit, err = units.RAMInBytes(c.String("memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory") - } - } - if c.String("memory-reservation") != "" { - memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-reservation") - } - } - if c.String("memory-swap") != "" { - memorySwap, err = units.RAMInBytes(c.String("memory-swap")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for memory-swap") - } - } - if c.String("kernel-memory") != "" { - memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for kernel-memory") - } - } - if c.String("blkio-weight") != "" { - u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) - if err != nil { - return nil, errors.Wrapf(err, "invalid value for blkio-weight") - } - blkioWeight = uint16(u) - } - var mountList []spec.Mount - if mountList, err = parseMounts(c.StringArray("mount")); err != nil { - return nil, err - } - - if err = parseVolumes(c.StringArray("volume")); err != nil { - return nil, err - } - - if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil { - return nil, err - } - - tty := c.Bool("tty") - - if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { - return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") - } - if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { - return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") - } - - // EXPOSED PORTS - var portBindings map[nat.Port][]nat.PortBinding - if data != nil { - portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.Config.ExposedPorts) - if err != nil { - return nil, err - } - } - - // Kernel Namespaces - // TODO Fix handling of namespace from pod - // Instead of integrating here, should be done in libpod - // However, that also involves setting up security opts - // when the pod's namespace is integrated - namespaceNet := c.String("network") - if c.Flag("net").Changed { - namespaceNet = c.String("net") - } - namespaces = map[string]string{ - "pid": c.String("pid"), - "net": namespaceNet, - "ipc": c.String("ipc"), - "user": c.String("userns"), - "uts": c.String("uts"), - } - - originalPodName := c.String("pod") - podName := strings.Replace(originalPodName, "new:", "", 1) - // after we strip out :new, make sure there is something left for a pod name - if len(podName) < 1 && c.IsSet("pod") { - return nil, errors.Errorf("new pod name must be at least one character") - } - if c.IsSet("pod") { - if strings.HasPrefix(originalPodName, "new:") { - if rootless.IsRootless() { - // To create a new pod, we must immediately create the userns. - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return nil, err - } - if became { - os.Exit(ret) - } - } - // pod does not exist; lets make it - var podOptions []libpod.PodCreateOption - podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) - if len(portBindings) > 0 { - ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) - if err != nil { - return nil, err - } - podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) - } - - podNsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) - if err != nil { - return nil, err - } - podOptions = append(podOptions, podNsOptions...) - // make pod - pod, err := runtime.NewPod(ctx, podOptions...) - if err != nil { - return nil, err - } - logrus.Debugf("pod %s created by new container request", pod.ID()) - - // The container now cannot have port bindings; so we reset the map - portBindings = make(map[nat.Port][]nat.PortBinding) - } - namespaces, err = configurePod(c, runtime, namespaces, podName) - if err != nil { - return nil, err - } - } - - pidMode := ns.PidMode(namespaces["pid"]) - if !cc.Valid(string(pidMode), pidMode) { - return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) - } - - usernsMode := ns.UsernsMode(namespaces["user"]) - if !cc.Valid(string(usernsMode), usernsMode) { - return nil, errors.Errorf("--userns %q is not valid", namespaces["user"]) - } - - utsMode := ns.UTSMode(namespaces["uts"]) - if !cc.Valid(string(utsMode), utsMode) { - return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"]) - } - - ipcMode := ns.IpcMode(namespaces["ipc"]) - if !cc.Valid(string(ipcMode), ipcMode) { - return nil, errors.Errorf("--ipc %q is not valid", ipcMode) - } - - // Make sure if network is set to container namespace, port binding is not also being asked for - netMode := ns.NetworkMode(namespaces["net"]) - if netMode.IsContainer() { - if len(portBindings) > 0 { - return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") - } - } - - // USER - user := c.String("user") - if user == "" { - if data == nil { - user = "0" - } else { - user = data.Config.User - } - } - - // STOP SIGNAL - stopSignal := syscall.SIGTERM - signalString := "" - if data != nil { - signalString = data.Config.StopSignal - } - if c.IsSet("stop-signal") { - signalString = c.String("stop-signal") - } - if signalString != "" { - stopSignal, err = signal.ParseSignal(signalString) - if err != nil { - return nil, err - } - } - - // ENVIRONMENT VARIABLES - env := defaultEnvVariables - if data != nil { - for _, e := range data.Config.Env { - split := strings.SplitN(e, "=", 2) - if len(split) > 1 { - env[split[0]] = split[1] - } else { - env[split[0]] = "" - } - } - } - if err := readKVStrings(env, c.StringSlice("env-file"), c.StringSlice("env")); err != nil { - return nil, errors.Wrapf(err, "unable to process environment variables") - } - - // LABEL VARIABLES - labels, err := getAllLabels(c.StringSlice("label-file"), c.StringSlice("label")) - if err != nil { - return nil, errors.Wrapf(err, "unable to process labels") - } - if data != nil { - for key, val := range data.Config.Labels { - if _, ok := labels[key]; !ok { - labels[key] = val - } - } - } - - // ANNOTATIONS - annotations := make(map[string]string) - // First, add our default annotations - annotations[ann.ContainerType] = "sandbox" - annotations[ann.TTY] = "false" - if tty { - annotations[ann.TTY] = "true" - } - if data != nil { - // Next, add annotations from the image - for key, value := range data.Annotations { - annotations[key] = value - } - } - // Last, add user annotations - for _, annotation := range c.StringSlice("annotation") { - splitAnnotation := strings.SplitN(annotation, "=", 2) - if len(splitAnnotation) < 2 { - return nil, errors.Errorf("Annotations must be formatted KEY=VALUE") - } - annotations[splitAnnotation[0]] = splitAnnotation[1] - } - - // WORKING DIRECTORY - workDir := "/" - if c.IsSet("workdir") || c.IsSet("w") { - workDir = c.String("workdir") - } else if data != nil && data.Config.WorkingDir != "" { - workDir = data.Config.WorkingDir - } - - entrypoint := configureEntrypoint(c, data) - // Build the command - // If we have an entry point, it goes first - if len(entrypoint) > 0 { - command = entrypoint - } - if len(inputCommand) > 0 { - // User command overrides data CMD - command = append(command, inputCommand...) - } else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") { - // If not user command, add CMD - command = append(command, data.Config.Cmd...) - } - - if data != nil && len(command) == 0 { - return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") - } - - // SHM Size - shmSize, err := units.FromHumanSize(c.String("shm-size")) - if err != nil { - return nil, errors.Wrapf(err, "unable to translate --shm-size") - } - - // Verify the additional hosts are in correct format - for _, host := range c.StringSlice("add-host") { - if _, err := validateExtraHost(host); err != nil { - return nil, err - } - } - - // Check for . and dns-search domains - if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { - return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") - } - - // Validate domains are good - for _, dom := range c.StringSlice("dns-search") { - if _, err := validateDomain(dom); err != nil { - return nil, err - } - } - - var ImageVolumes map[string]struct{} - if data != nil && c.String("image-volume") != "ignore" { - ImageVolumes = data.Config.Volumes - } - - var imageVolType = map[string]string{ - "bind": "", - "tmpfs": "", - "ignore": "", - } - if _, ok := imageVolType[c.String("image-volume")]; !ok { - return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) - } - - var systemd bool - if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { - systemd = true - if signalString == "" { - stopSignal, err = signal.ParseSignal("RTMIN+3") - if err != nil { - return nil, errors.Wrapf(err, "error parsing systemd signal") - } - } - } - // 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 { - network = c.String("net") - } - - var memorySwappiness int64 - if c.Flags().Lookup("memory-swappiness") != nil { - memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness") - } - config := &cc.CreateConfig{ - Runtime: runtime, - Annotations: annotations, - BuiltinImgVolumes: ImageVolumes, - ConmonPidFile: c.String("conmon-pidfile"), - ImageVolumeType: c.String("image-volume"), - CapAdd: c.StringSlice("cap-add"), - CapDrop: c.StringSlice("cap-drop"), - CgroupParent: c.String("cgroup-parent"), - Command: command, - Detach: c.Bool("detach"), - Devices: c.StringSlice("device"), - DNSOpt: c.StringSlice("dns-opt"), - DNSSearch: c.StringSlice("dns-search"), - DNSServers: c.StringSlice("dns"), - Entrypoint: entrypoint, - Env: env, - //ExposedPorts: ports, - GroupAdd: c.StringSlice("group-add"), - Hostname: c.String("hostname"), - HostAdd: c.StringSlice("add-host"), - IDMappings: idmappings, - Image: imageName, - ImageID: imageID, - Interactive: c.Bool("interactive"), - //IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6 - IPAddress: c.String("ip"), - Labels: labels, - //LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet - LogDriver: c.String("log-driver"), - LogDriverOpt: c.StringSlice("log-opt"), - MacAddress: c.String("mac-address"), - Name: c.String("name"), - Network: network, - //NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman? - IpcMode: ipcMode, - NetMode: netMode, - UtsMode: utsMode, - PidMode: pidMode, - Pod: podName, - Privileged: c.Bool("privileged"), - Publish: c.StringSlice("publish"), - PublishAll: c.Bool("publish-all"), - PortBindings: portBindings, - Quiet: c.Bool("quiet"), - ReadOnlyRootfs: c.Bool("read-only"), - Resources: cc.CreateResourceConfig{ - BlkioWeight: blkioWeight, - BlkioWeightDevice: c.StringSlice("blkio-weight-device"), - CPUShares: c.Uint64("cpu-shares"), - CPUPeriod: c.Uint64("cpu-period"), - CPUsetCPUs: c.String("cpuset-cpus"), - CPUsetMems: c.String("cpuset-mems"), - CPUQuota: c.Int64("cpu-quota"), - CPURtPeriod: c.Uint64("cpu-rt-period"), - CPURtRuntime: c.Int64("cpu-rt-runtime"), - CPUs: c.Float64("cpus"), - DeviceReadBps: c.StringSlice("device-read-bps"), - DeviceReadIOps: c.StringSlice("device-read-iops"), - DeviceWriteBps: c.StringSlice("device-write-bps"), - DeviceWriteIOps: c.StringSlice("device-write-iops"), - DisableOomKiller: c.Bool("oom-kill-disable"), - ShmSize: shmSize, - Memory: memoryLimit, - MemoryReservation: memoryReservation, - MemorySwap: memorySwap, - MemorySwappiness: int(memorySwappiness), - KernelMemory: memoryKernel, - OomScoreAdj: c.Int("oom-score-adj"), - PidsLimit: c.Int64("pids-limit"), - Ulimit: c.StringSlice("ulimit"), - }, - Rm: c.Bool("rm"), - StopSignal: stopSignal, - StopTimeout: c.Uint("stop-timeout"), - Sysctl: sysctl, - Systemd: systemd, - Tmpfs: c.StringSlice("tmpfs"), - Tty: tty, - User: user, - UsernsMode: usernsMode, - Mounts: mountList, - Volumes: c.StringArray("volume"), - WorkDir: workDir, - Rootfs: rootfs, - VolumesFrom: c.StringSlice("volumes-from"), - Syslog: c.GlobalFlags.Syslog, - } - if c.Bool("init") { - initPath := c.String("init-path") - if initPath == "" { - initPath = runtime.GetConfig().InitPath - } - if err := config.AddContainerInitBinary(initPath); err != nil { - return nil, err - } - } - - if config.Privileged { - config.LabelOpts = label.DisableSecOpt() - } else { - if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil { - return nil, err - } - } - config.SecurityOpts = c.StringArray("security-opt") - warnings, err := verifyContainerResources(config, false) - if err != nil { - return nil, err - } - for _, warning := range warnings { - fmt.Fprintln(os.Stderr, warning) - } - return config, nil -} - -type namespace interface { - IsContainer() bool - 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)) - } - } - 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 { - return nil, err - } - - options, err := createConfig.GetContainerCreateOptions(r, pod) - 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 { - return nil, err - } - - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return nil, err - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return nil, err - } - return ctr, nil -} diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go index e232d7e66..e77e562d4 100644 --- a/cmd/podman/diff.go +++ b/cmd/podman/diff.go @@ -2,8 +2,8 @@ package main import ( "fmt" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" @@ -34,8 +34,7 @@ func (so stdoutStruct) Out() error { var ( diffCommand cliconfig.DiffValues - diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The - container or image will be compared to its parent layer`) + diffDescription = fmt.Sprint(`Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.`) _diffCommand = &cobra.Command{ Use: "diff [flags] CONTAINER | IMAGE", @@ -54,6 +53,7 @@ var ( func init() { diffCommand.Command = _diffCommand + diffCommand.SetHelpTemplate(HelpTemplate()) diffCommand.SetUsageTemplate(UsageTemplate()) flags := diffCommand.Flags() diff --git a/cmd/podman/errors.go b/cmd/podman/errors.go index 2572b8779..9731037f4 100644 --- a/cmd/podman/errors.go +++ b/cmd/podman/errors.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package main import ( @@ -13,7 +15,8 @@ func outputError(err error) { if MainGlobalOpts.LogLevel == "debug" { logrus.Errorf(err.Error()) } else { - if ee, ok := err.(*exec.ExitError); ok { + ee, ok := err.(*exec.ExitError) + if ok { if status, ok := ee.Sys().(syscall.WaitStatus); ok { exitCode = status.ExitStatus() } diff --git a/cmd/podman/errors_remote.go b/cmd/podman/errors_remote.go new file mode 100644 index 000000000..ab255ea56 --- /dev/null +++ b/cmd/podman/errors_remote.go @@ -0,0 +1,43 @@ +// +build remoteclient + +package main + +import ( + "fmt" + "os" + "os/exec" + "syscall" + + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func outputError(err error) { + if MainGlobalOpts.LogLevel == "debug" { + logrus.Errorf(err.Error()) + } else { + if ee, ok := err.(*exec.ExitError); ok { + if status, ok := ee.Sys().(syscall.WaitStatus); ok { + exitCode = status.ExitStatus() + } + } + var ne error + switch e := err.(type) { + // For some reason golang wont let me list them with commas so listing them all. + case *iopodman.ImageNotFound: + ne = errors.New(e.Reason) + case *iopodman.ContainerNotFound: + ne = errors.New(e.Reason) + case *iopodman.PodNotFound: + ne = errors.New(e.Reason) + case *iopodman.VolumeNotFound: + ne = errors.New(e.Reason) + case *iopodman.ErrorOccurred: + ne = errors.New(e.Reason) + default: + ne = err + } + fmt.Fprintln(os.Stderr, "Error:", ne.Error()) + } +} diff --git a/cmd/podman/events.go b/cmd/podman/events.go new file mode 100644 index 000000000..f6c20e8ff --- /dev/null +++ b/cmd/podman/events.go @@ -0,0 +1,49 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + eventsCommand cliconfig.EventValues + eventsDescription = "Monitor podman events" + _eventsCommand = &cobra.Command{ + Use: "events", + Args: noSubArgs, + Short: "show podman events", + Long: eventsDescription, + RunE: func(cmd *cobra.Command, args []string) error { + eventsCommand.InputArgs = args + eventsCommand.GlobalFlags = MainGlobalOpts + return eventsCmd(&eventsCommand) + }, + Example: `podman events + podman events --filter event=create + podman events --since 1h30s`, + } +) + +func init() { + eventsCommand.Command = _eventsCommand + eventsCommand.SetUsageTemplate(UsageTemplate()) + flags := eventsCommand.Flags() + flags.StringArrayVar(&eventsCommand.Filter, "filter", []string{}, "filter output") + flags.StringVar(&eventsCommand.Format, "format", "", "format the output using a Go template") + flags.BoolVar(&eventsCommand.Stream, "stream", true, "stream new events; for testing only") + flags.StringVar(&eventsCommand.Since, "since", "", "show all events created since timestamp") + flags.StringVar(&eventsCommand.Until, "until", "", "show all events until timestamp") + flags.MarkHidden("stream") +} + +func eventsCmd(c *cliconfig.EventValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) + + return runtime.Events(c) +} diff --git a/cmd/podman/exec.go b/cmd/podman/exec.go index 4917fb606..a6afbf75a 100644 --- a/cmd/podman/exec.go +++ b/cmd/podman/exec.go @@ -2,23 +2,23 @@ package main import ( "fmt" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/spf13/cobra" + "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/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" + "github.com/spf13/cobra" ) var ( execCommand cliconfig.ExecValues - execDescription = ` - podman exec - - Run a command in a running container + execDescription = `Execute the specified command inside a running container. ` _execCommand = &cobra.Command{ Use: "exec [flags] CONTAINER [COMMAND [ARG...]]", @@ -37,16 +37,18 @@ var ( func init() { execCommand.Command = _execCommand + execCommand.SetHelpTemplate(HelpTemplate()) execCommand.SetUsageTemplate(UsageTemplate()) flags := execCommand.Flags() flags.SetInterspersed(false) - flags.StringSliceVarP(&execCommand.Env, "env", "e", []string{}, "Set environment variables") + flags.StringArrayVarP(&execCommand.Env, "env", "e", []string{}, "Set environment variables") flags.BoolVarP(&execCommand.Interfactive, "interactive", "i", false, "Not supported. All exec commands are interactive by default") flags.BoolVarP(&execCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.BoolVar(&execCommand.Privileged, "privileged", false, "Give the process extended Linux capabilities inside the container. The default is false") flags.BoolVarP(&execCommand.Tty, "tty", "t", false, "Allocate a pseudo-TTY. The default is false") flags.StringVarP(&execCommand.User, "user", "u", "", "Sets the username or UID used and optionally the groupname or GID for the specified command") + flags.IntVar(&execCommand.PreserveFDs, "preserve-fds", 0, "Pass N additional file descriptors to the container") flags.StringVarP(&execCommand.Workdir, "workdir", "w", "", "Working directory inside the container") markFlagHiddenForRemoteClient("latest", flags) } @@ -82,22 +84,54 @@ func execCmd(c *cliconfig.ExecValues) error { return errors.Wrapf(err, "unable to exec into %s", args[0]) } - pid, err := ctr.PID() - if err != nil { - return err - } - became, ret, err := rootless.JoinNS(uint(pid)) - if err != nil { - return err + if c.PreserveFDs > 0 { + entries, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return errors.Wrapf(err, "unable to read /proc/self/fd") + } + m := make(map[int]bool) + for _, e := range entries { + i, err := strconv.Atoi(e.Name()) + if err != nil { + if err != nil { + return errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name()) + } + } + m[i] = true + } + for i := 3; i < 3+c.PreserveFDs; i++ { + if _, found := m[i]; !found { + return errors.New("invalid --preserve-fds=N specified. Not enough FDs available") + } + } + } - if became { - os.Exit(ret) + + if os.Geteuid() != 0 { + var became bool + var ret int + + 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) + } } // ENVIRONMENT VARIABLES env := map[string]string{} - if err := readKVStrings(env, []string{}, c.Env); err != nil { + if err := parse.ReadKVStrings(env, []string{}, c.Env); err != nil { return errors.Wrapf(err, "unable to process environment variables") } envs := []string{} @@ -113,5 +147,5 @@ func execCmd(c *cliconfig.ExecValues) error { streams.AttachError = true streams.AttachInput = true - return ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir, streams) + return ctr.Exec(c.Tty, c.Privileged, envs, cmd, c.User, c.Workdir, streams, c.PreserveFDs) } diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go index 109831e74..8a2f78c88 100644 --- a/cmd/podman/exists.go +++ b/cmd/podman/exists.go @@ -16,21 +16,12 @@ var ( containerExistsCommand cliconfig.ContainerExistsValues podExistsCommand cliconfig.PodExistsValues - imageExistsDescription = ` - podman image exists + imageExistsDescription = `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.` - Check if an image exists in local storage -` - containerExistsDescription = ` - podman container exists + containerExistsDescription = `If the named container exists in local storage, podman container exists exits with 0, otherwise the exit code will be 1.` - Check if a container exists in local storage -` - podExistsDescription = ` - podman pod exists + podExistsDescription = `If the named pod exists in local storage, podman pod exists exits with 0, otherwise the exit code will be 1.` - Check if a pod exists in local storage -` _imageExistsCommand = &cobra.Command{ Use: "exists IMAGE", Short: "Check if an image exists in local storage", @@ -75,12 +66,15 @@ var ( func init() { imageExistsCommand.Command = _imageExistsCommand imageExistsCommand.DisableFlagsInUseLine = true + imageExistsCommand.SetHelpTemplate(HelpTemplate()) imageExistsCommand.SetUsageTemplate(UsageTemplate()) containerExistsCommand.Command = _containerExistsCommand containerExistsCommand.DisableFlagsInUseLine = true + containerExistsCommand.SetHelpTemplate(HelpTemplate()) containerExistsCommand.SetUsageTemplate(UsageTemplate()) podExistsCommand.Command = _podExistsCommand podExistsCommand.DisableFlagsInUseLine = true + podExistsCommand.SetHelpTemplate(HelpTemplate()) podExistsCommand.SetUsageTemplate(UsageTemplate()) } diff --git a/cmd/podman/export.go b/cmd/podman/export.go index d40c05019..92633facd 100644 --- a/cmd/podman/export.go +++ b/cmd/podman/export.go @@ -4,6 +4,7 @@ import ( "os" "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" @@ -32,9 +33,10 @@ var ( func init() { exportCommand.Command = _exportCommand + exportCommand.SetHelpTemplate(HelpTemplate()) exportCommand.SetUsageTemplate(UsageTemplate()) flags := exportCommand.Flags() - flags.StringVarP(&exportCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.StringVarP(&exportCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") } // exportCmd saves a container to a tarball on disk @@ -58,18 +60,19 @@ func exportCmd(c *cliconfig.ExportValues) error { } output := c.Output - if runtime.Remote && (output == "/dev/stdout" || len(output) == 0) { + if runtime.Remote && len(output) == 0 { return errors.New("remote client usage must specify an output file (-o)") } - if output == "/dev/stdout" { + if len(output) == 0 { file := os.Stdout if logrus.IsTerminal(file) { return errors.Errorf("refusing to export to terminal. Use -o flag or redirect") } + output = "/dev/stdout" } - if err := validateFileName(output); err != nil { + if err := parse.ValidateFileName(output); err != nil { return err } return runtime.Export(args[0], output) diff --git a/cmd/podman/formats/formats_test.go b/cmd/podman/formats/formats_test.go deleted file mode 100644 index c75109d65..000000000 --- a/cmd/podman/formats/formats_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package formats - -import ( - "bytes" - "strings" - "testing" - - "github.com/containers/libpod/pkg/inspect" -) - -func TestSetJSONFormatEncoder(t *testing.T) { - tt := []struct { - name string - imageData *inspect.ImageData - expected string - isTerminal bool - }{ - { - name: "HTML tags are not escaped", - imageData: &inspect.ImageData{Author: "dave <dave@corp.io>"}, - expected: `"Author": "dave <dave@corp.io>"`, - isTerminal: true, - }, - { - name: "HTML tags are escaped", - imageData: &inspect.ImageData{Author: "dave <dave@corp.io>"}, - expected: `"Author": "dave \u003cdave@corp.io\u003e"`, - isTerminal: false, - }, - } - - for _, tc := range tt { - buf := bytes.NewBuffer(nil) - enc := setJSONFormatEncoder(tc.isTerminal, buf) - if err := enc.Encode(tc.imageData); err != nil { - t.Errorf("test %#v failed encoding: %s", tc.name, err) - } - if !strings.Contains(buf.String(), tc.expected) { - t.Errorf("test %#v expected output to contain %#v. Output:\n%v\n", tc.name, tc.expected, buf.String()) - } - } -} diff --git a/cmd/podman/generate.go b/cmd/podman/generate.go index 773d625ee..197fd26a6 100644 --- a/cmd/podman/generate.go +++ b/cmd/podman/generate.go @@ -12,6 +12,7 @@ var ( Use: "generate", Short: "Generated structured data", Long: generateDescription, + RunE: commandRunE(), } ) diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index fa2872b77..42cfba8d8 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -15,10 +15,12 @@ import ( var ( containerKubeCommand cliconfig.GenerateKubeValues - containerKubeDescription = "Generate Kubernetes Pod YAML" - _containerKubeCommand = &cobra.Command{ + containerKubeDescription = `Command generates Kubernetes Pod YAML (v1 specification) from a podman container or pod. + + Whether the input is for a container or pod, Podman will always generate the specification as a Pod. The input may be in the form of a pod or container name or ID.` + _containerKubeCommand = &cobra.Command{ Use: "kube [flags] CONTAINER | POD", - Short: "Generate Kubernetes pod YAML for a container or pod", + Short: "Generate Kubernetes pod YAML from a container or pod", Long: containerKubeDescription, RunE: func(cmd *cobra.Command, args []string) error { containerKubeCommand.InputArgs = args @@ -33,6 +35,7 @@ var ( func init() { containerKubeCommand.Command = _containerKubeCommand + containerKubeCommand.SetHelpTemplate(HelpTemplate()) containerKubeCommand.SetUsageTemplate(UsageTemplate()) flags := containerKubeCommand.Flags() flags.BoolVarP(&containerKubeCommand.Service, "service", "s", false, "Generate YAML for kubernetes service object") @@ -54,8 +57,8 @@ func generateKubeYAMLCmd(c *cliconfig.GenerateKubeValues) error { return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") } args := c.InputArgs - if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { - return errors.Errorf("you must provide one container|pod ID or name or --latest") + if len(args) != 1 { + return errors.Errorf("you must provide exactly one container|pod ID or name") } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) diff --git a/cmd/podman/healthcheck.go b/cmd/podman/healthcheck.go new file mode 100644 index 000000000..48d6b6bbf --- /dev/null +++ b/cmd/podman/healthcheck.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/spf13/cobra" +) + +var healthcheckDescription = "Manage health checks on containers" +var healthcheckCommand = cliconfig.PodmanCommand{ + Command: &cobra.Command{ + Use: "healthcheck", + Short: "Manage Healthcheck", + Long: healthcheckDescription, + RunE: commandRunE(), + }, +} + +// Commands that are universally implemented +var healthcheckCommands []*cobra.Command + +func init() { + healthcheckCommand.AddCommand(healthcheckCommands...) + healthcheckCommand.AddCommand(getHealtcheckSubCommands()...) + healthcheckCommand.SetUsageTemplate(UsageTemplate()) + rootCmd.AddCommand(healthcheckCommand.Command) +} diff --git a/cmd/podman/healthcheck_run.go b/cmd/podman/healthcheck_run.go new file mode 100644 index 000000000..bd3a4ea15 --- /dev/null +++ b/cmd/podman/healthcheck_run.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + healthcheckRunCommand cliconfig.HealthCheckValues + healthcheckRunDescription = "run the health check of a container" + _healthcheckrunCommand = &cobra.Command{ + Use: "run [flags] CONTAINER", + Short: "run the health check of a container", + Long: healthcheckRunDescription, + Example: `podman healthcheck run mywebapp`, + RunE: func(cmd *cobra.Command, args []string) error { + healthcheckRunCommand.InputArgs = args + healthcheckRunCommand.GlobalFlags = MainGlobalOpts + return healthCheckCmd(&healthcheckRunCommand) + }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 || len(args) > 1 { + return errors.New("must provide the name or ID of one container") + } + return nil + }, + } +) + +func init() { + healthcheckRunCommand.Command = _healthcheckrunCommand + healthcheckRunCommand.SetUsageTemplate(UsageTemplate()) +} + +func healthCheckCmd(c *cliconfig.HealthCheckValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrap(err, "could not get runtime") + } + status, err := runtime.HealthCheck(c) + if err != nil { + if status == libpod.HealthCheckFailure { + fmt.Println("\nunhealthy") + } + return err + } + fmt.Println("healthy") + return nil +} diff --git a/cmd/podman/history.go b/cmd/podman/history.go index 533ee91cb..4b76ef0ca 100644 --- a/cmd/podman/history.go +++ b/cmd/podman/history.go @@ -6,8 +6,8 @@ import ( "strings" "time" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter" "github.com/docker/go-units" @@ -37,8 +37,9 @@ type historyOptions struct { var ( historyCommand cliconfig.HistoryValues - historyDescription = "Displays the history of an image. The information can be printed out in an easy to read, " + - "or user specified format, and can be truncated." + historyDescription = `Displays the history of an image. + + The information can be printed out in an easy to read, or user specified format, and can be truncated.` _historyCommand = &cobra.Command{ Use: "history [flags] IMAGE", Short: "Show history of a specified image", @@ -53,6 +54,7 @@ var ( func init() { historyCommand.Command = _historyCommand + historyCommand.SetHelpTemplate(HelpTemplate()) historyCommand.SetUsageTemplate(UsageTemplate()) flags := historyCommand.Flags() flags.StringVar(&historyCommand.Format, "format", "", "Change the output to JSON or a Go template") diff --git a/cmd/podman/image.go b/cmd/podman/image.go index b5c1c3ccf..66c141686 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -1,6 +1,8 @@ package main import ( + "strings" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/spf13/cobra" ) @@ -12,38 +14,80 @@ var ( Use: "image", Short: "Manage images", Long: imageDescription, + RunE: commandRunE(), + }, + } + imagesSubCommand cliconfig.ImagesValues + _imagesSubCommand = &cobra.Command{ + Use: strings.Replace(_imagesCommand.Use, "images", "list", 1), + Short: _imagesCommand.Short, + Long: _imagesCommand.Long, + Aliases: []string{"ls"}, + RunE: func(cmd *cobra.Command, args []string) error { + imagesSubCommand.InputArgs = args + imagesSubCommand.GlobalFlags = MainGlobalOpts + return imagesCmd(&imagesSubCommand) }, + Example: strings.Replace(_imagesCommand.Example, "podman images", "podman image list", -1), + } + + inspectSubCommand cliconfig.InspectValues + _inspectSubCommand = &cobra.Command{ + Use: strings.Replace(_inspectCommand.Use, "CONTAINER | ", "", 1), + Short: "Display the configuration of an image", + Long: `Displays the low-level information on an image identified by name or ID.`, + RunE: func(cmd *cobra.Command, args []string) error { + inspectSubCommand.InputArgs = args + inspectSubCommand.GlobalFlags = MainGlobalOpts + return inspectCmd(&inspectSubCommand) + }, + Example: `podman image inspect alpine`, + } + + rmSubCommand cliconfig.RmiValues + _rmSubCommand = &cobra.Command{ + Use: strings.Replace(_rmiCommand.Use, "rmi", "rm", 1), + Short: _rmiCommand.Short, + Long: _rmiCommand.Long, + RunE: func(cmd *cobra.Command, args []string) error { + rmSubCommand.InputArgs = args + rmSubCommand.GlobalFlags = MainGlobalOpts + return rmiCmd(&rmSubCommand) + }, + Example: strings.Replace(_rmiCommand.Example, "podman rmi", "podman image rm", -1), } - _imagesSubCommand = _imagesCommand - _rmSubCommand = _rmiCommand ) //imageSubCommands are implemented both in local and remote clients var imageSubCommands = []*cobra.Command{ _buildCommand, _historyCommand, + _imagesSubCommand, _imageExistsCommand, _importCommand, - _inspectCommand, + _inspectSubCommand, _loadCommand, _pruneImagesCommand, _pullCommand, _pushCommand, + _rmSubCommand, _saveCommand, _tagCommand, + _treeCommand, } func init() { + rmSubCommand.Command = _rmSubCommand + rmiInit(&rmSubCommand) + + imagesSubCommand.Command = _imagesSubCommand + imagesInit(&imagesSubCommand) + + inspectSubCommand.Command = _inspectSubCommand + inspectInit(&inspectSubCommand) + imageCommand.SetUsageTemplate(UsageTemplate()) imageCommand.AddCommand(imageSubCommands...) imageCommand.AddCommand(getImageSubCommands()...) - // Setup of "images" to appear as "list" - _imagesSubCommand.Use = "list" - _imagesSubCommand.Aliases = []string{"ls"} - imageCommand.AddCommand(&_imagesSubCommand) - - // Setup of "rmi" to appears as "rm" - _rmSubCommand.Use = "rm" - imageCommand.AddCommand(&_rmSubCommand) } diff --git a/cmd/podman/imagefilters/filters.go b/cmd/podman/imagefilters/filters.go index d01eb7436..2932d61c0 100644 --- a/cmd/podman/imagefilters/filters.go +++ b/cmd/podman/imagefilters/filters.go @@ -2,11 +2,14 @@ package imagefilters import ( "context" + "fmt" + "path/filepath" "strings" "time" "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/inspect" + "github.com/sirupsen/logrus" ) // ResultFilter is a mock function for image filtering @@ -61,6 +64,27 @@ func LabelFilter(ctx context.Context, labelfilter string) ResultFilter { } } +// ReferenceFilter allows you to filter by image name +// Replacing all '/' with '|' so that filepath.Match() can work +// '|' character is not valid in image name, so this is safe +func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter { + filter := fmt.Sprintf("*%s*", referenceFilter) + filter = strings.Replace(filter, "/", "|", -1) + return func(i *adapter.ContainerImage) bool { + for _, name := range i.Names() { + newName := strings.Replace(name, "/", "|", -1) + match, err := filepath.Match(filter, newName) + if err != nil { + logrus.Errorf("failed to match %s and %s, %q", name, referenceFilter, err) + } + if match { + return true + } + } + return false + } +} + // OutputImageFilter allows you to filter by an a specific image name func OutputImageFilter(userImage *adapter.ContainerImage) ResultFilter { return func(i *adapter.ContainerImage) bool { diff --git a/cmd/podman/images.go b/cmd/podman/images.go index e6f4d9a60..6133450be 100644 --- a/cmd/podman/images.go +++ b/cmd/podman/images.go @@ -2,14 +2,15 @@ package main import ( "context" + "fmt" "reflect" "sort" "strings" "time" "unicode" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/imagefilters" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter" @@ -85,7 +86,7 @@ func (a imagesSortedSize) Less(i, j int) bool { var ( imagesCommand cliconfig.ImagesValues - imagesDescription = "lists locally stored images." + imagesDescription = "Lists images previously pulled to the system or created on the system." _imagesCommand = cobra.Command{ Use: "images [flags] [IMAGE]", @@ -102,28 +103,33 @@ var ( } ) -func init() { - imagesCommand.Command = &_imagesCommand - imagesCommand.SetUsageTemplate(UsageTemplate()) - - flags := imagesCommand.Flags() - flags.BoolVarP(&imagesCommand.All, "all", "a", false, "Show all images (default hides intermediate images)") - flags.BoolVar(&imagesCommand.Digests, "digests", false, "Show digests") - flags.StringSliceVarP(&imagesCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") - flags.StringVar(&imagesCommand.Format, "format", "", "Change the output format to JSON or a Go template") - flags.BoolVarP(&imagesCommand.Noheading, "noheading", "n", false, "Do not print column headings") +func imagesInit(command *cliconfig.ImagesValues) { + command.SetHelpTemplate(HelpTemplate()) + command.SetUsageTemplate(UsageTemplate()) + + flags := command.Flags() + flags.BoolVarP(&command.All, "all", "a", false, "Show all images (default hides intermediate images)") + flags.BoolVar(&command.Digests, "digests", false, "Show digests") + flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])") + flags.StringVar(&command.Format, "format", "", "Change the output format to JSON or a Go template") + flags.BoolVarP(&command.Noheading, "noheading", "n", false, "Do not print column headings") // TODO Need to learn how to deal with second name being a string instead of a char. // This needs to be "no-trunc, notruncate" - flags.BoolVar(&imagesCommand.NoTrunc, "no-trunc", false, "Do not truncate output") - flags.BoolVarP(&imagesCommand.Quiet, "quiet", "q", false, "Display only image IDs") - flags.StringVar(&imagesCommand.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") + flags.BoolVar(&command.NoTrunc, "no-trunc", false, "Do not truncate output") + flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Display only image IDs") + flags.StringVar(&command.Sort, "sort", "created", "Sort by created, id, repository, size, or tag") } +func init() { + imagesCommand.Command = &_imagesCommand + imagesInit(&imagesCommand) +} + func imagesCmd(c *cliconfig.ImagesValues) error { var ( filterFuncs []imagefilters.ResultFilter - newImage *adapter.ContainerImage + image string ) runtime, err := adapter.GetRuntime(&c.PodmanCommand) @@ -132,23 +138,23 @@ func imagesCmd(c *cliconfig.ImagesValues) error { } defer runtime.Shutdown(false) if len(c.InputArgs) == 1 { - newImage, err = runtime.NewImageFromLocal(c.InputArgs[0]) - if err != nil { - return err - } + image = c.InputArgs[0] } - if len(c.InputArgs) > 1 { return errors.New("'podman images' requires at most 1 argument") } - + if len(c.Filter) > 0 && image != "" { + return errors.New("can not specify an image and a filter") + } ctx := getContext() - if len(c.Filter) > 0 || newImage != nil { - filterFuncs, err = CreateFilterFuncs(ctx, runtime, c.Filter, newImage) - if err != nil { - return err - } + if len(c.Filter) > 0 { + filterFuncs, err = CreateFilterFuncs(ctx, runtime, c.Filter, nil) + } else { + filterFuncs, err = CreateFilterFuncs(ctx, runtime, []string{fmt.Sprintf("reference=%s", image)}, nil) + } + if err != nil { + return err } opts := imagesOptions{ @@ -169,7 +175,7 @@ func imagesCmd(c *cliconfig.ImagesValues) error { var filteredImages []*adapter.ContainerImage //filter the images - if len(c.Filter) > 0 || newImage != nil { + if len(c.Filter) > 0 || len(c.InputArgs) == 1 { filteredImages = imagefilters.FilterImages(images, filterFuncs) } else { filteredImages = images @@ -371,6 +377,9 @@ func CreateFilterFuncs(ctx context.Context, r *adapter.LocalRuntime, filters []s case "label": labelFilter := strings.Join(splitFilter[1:], "=") filterFuncs = append(filterFuncs, imagefilters.LabelFilter(ctx, labelFilter)) + case "reference": + referenceFilter := strings.Join(splitFilter[1:], "=") + filterFuncs = append(filterFuncs, imagefilters.ReferenceFilter(ctx, referenceFilter)) default: return nil, errors.Errorf("invalid filter %s ", splitFilter[0]) } diff --git a/cmd/podman/images_prune.go b/cmd/podman/images_prune.go index 79dcd097c..b6f335fb3 100644 --- a/cmd/podman/images_prune.go +++ b/cmd/podman/images_prune.go @@ -11,13 +11,12 @@ import ( var ( pruneImagesCommand cliconfig.PruneImagesValues - pruneImagesDescription = ` - podman image prune + pruneImagesDescription = `Removes all unnamed images from local storage. - Removes all unnamed images from local storage -` + If an image is not being used by a container, it will be removed from the system.` _pruneImagesCommand = &cobra.Command{ Use: "prune", + Args: noSubArgs, Short: "Remove unused images", Long: pruneImagesDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -30,6 +29,7 @@ var ( func init() { pruneImagesCommand.Command = _pruneImagesCommand + pruneImagesCommand.SetHelpTemplate(HelpTemplate()) pruneImagesCommand.SetUsageTemplate(UsageTemplate()) flags := pruneImagesCommand.Flags() flags.BoolVarP(&pruneImagesCommand.All, "all", "a", false, "Remove all unused images, not just dangling ones") diff --git a/cmd/podman/import.go b/cmd/podman/import.go index ddf1bd802..f3fb7c988 100644 --- a/cmd/podman/import.go +++ b/cmd/podman/import.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -13,9 +14,9 @@ var ( importCommand cliconfig.ImportValues importDescription = `Create a container image from the contents of the specified tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz). - Note remote tar balls can be specified, via web address. - Optionally tag the image. You can specify the instructions using the --change option. - ` + + Note remote tar balls can be specified, via web address. + Optionally tag the image. You can specify the instructions using the --change option.` _importCommand = &cobra.Command{ Use: "import [flags] PATH [REFERENCE]", Short: "Import a tarball to create a filesystem image", @@ -33,6 +34,7 @@ var ( func init() { importCommand.Command = _importCommand + importCommand.SetHelpTemplate(HelpTemplate()) importCommand.SetUsageTemplate(UsageTemplate()) flags := importCommand.Flags() flags.StringSliceVarP(&importCommand.Change, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR") @@ -66,7 +68,7 @@ func importCmd(c *cliconfig.ImportValues) error { return errors.Errorf("too many arguments. Usage TARBALL [REFERENCE]") } - if err := validateFileName(source); err != nil { + if err := parse.ValidateFileName(source); err != nil { return err } diff --git a/cmd/podman/info.go b/cmd/podman/info.go index a1473dac9..195267c7f 100644 --- a/cmd/podman/info.go +++ b/cmd/podman/info.go @@ -4,8 +4,8 @@ import ( "fmt" rt "runtime" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/version" @@ -16,11 +16,15 @@ import ( var ( infoCommand cliconfig.InfoValues - infoDescription = "Display podman system information" - _infoCommand = &cobra.Command{ + infoDescription = `Display information pertaining to the host, current storage stats, and build of podman. + + Useful for the user and when reporting issues. +` + _infoCommand = &cobra.Command{ Use: "info", + Args: noSubArgs, Long: infoDescription, - Short: `Display information pertaining to the host, current storage stats, and build of podman. Useful for the user and when reporting issues.`, + Short: "Display podman system information", RunE: func(cmd *cobra.Command, args []string) error { infoCommand.InputArgs = args infoCommand.GlobalFlags = MainGlobalOpts @@ -32,6 +36,7 @@ var ( func init() { infoCommand.Command = _infoCommand + infoCommand.SetHelpTemplate(HelpTemplate()) infoCommand.SetUsageTemplate(UsageTemplate()) flags := infoCommand.Flags() diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 1c93a03e1..3d6fd07e0 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -5,8 +5,8 @@ import ( "encoding/json" "strings" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/adapter" cc "github.com/containers/libpod/pkg/spec" @@ -24,8 +24,10 @@ const ( var ( inspectCommand cliconfig.InspectValues - inspectDescription = "This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for unspecified type." - _inspectCommand = &cobra.Command{ + inspectDescription = `This displays the low-level information on containers and images identified by name or ID. + + If given a name that matches both a container and an image, this command inspects the container. By default, this will render all results in a JSON array.` + _inspectCommand = cobra.Command{ Use: "inspect [flags] CONTAINER | IMAGE", Short: "Display the configuration of a container or image", Long: inspectDescription, @@ -40,15 +42,34 @@ var ( } ) +func inspectInit(command *cliconfig.InspectValues) { + command.SetHelpTemplate(HelpTemplate()) + command.SetUsageTemplate(UsageTemplate()) + flags := command.Flags() + flags.StringVarP(&command.Format, "format", "f", "", "Change the output format to a Go template") + + // -t flag applicable only to 'podman inspect', not 'image/container inspect' + ambiguous := strings.Contains(command.Use, "|") + if ambiguous { + flags.StringVarP(&command.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (image or container)") + } + + if strings.Contains(command.Use, "CONTAINER") { + containers_only := " (containers only)" + if !ambiguous { + containers_only = "" + command.TypeObject = inspectTypeContainer + } + flags.BoolVarP(&command.Latest, "latest", "l", false, "Act on the latest container podman is aware of"+containers_only) + flags.BoolVarP(&command.Size, "size", "s", false, "Display total file size"+containers_only) + markFlagHiddenForRemoteClient("latest", flags) + } else { + command.TypeObject = inspectTypeImage + } +} func init() { - inspectCommand.Command = _inspectCommand - inspectCommand.SetUsageTemplate(UsageTemplate()) - flags := inspectCommand.Flags() - flags.StringVarP(&inspectCommand.TypeObject, "type", "t", inspectAll, "Return JSON for specified type, (e.g image, container or task)") - flags.StringVarP(&inspectCommand.Format, "format", "f", "", "Change the output format to a Go template") - flags.BoolVarP(&inspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of if the type is a container") - flags.BoolVarP(&inspectCommand.Size, "size", "s", false, "Display total file size if the type is container") - markFlagHiddenForRemoteClient("latest", flags) + inspectCommand.Command = &_inspectCommand + inspectInit(&inspectCommand) } func inspectCmd(c *cliconfig.InspectValues) error { diff --git a/cmd/podman/kill.go b/cmd/podman/kill.go index 76d2516b7..2c1e13eaf 100644 --- a/cmd/podman/kill.go +++ b/cmd/podman/kill.go @@ -2,16 +2,15 @@ package main import ( "fmt" - "syscall" + "reflect" + + "github.com/containers/libpod/pkg/adapter" + "github.com/opentracing/opentracing-go" "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/docker/docker/pkg/signal" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -39,6 +38,7 @@ var ( func init() { killCommand.Command = _killCommand + killCommand.SetHelpTemplate(HelpTemplate()) killCommand.SetUsageTemplate(UsageTemplate()) flags := killCommand.Flags() @@ -51,54 +51,44 @@ func init() { // killCmd kills one or more containers with a signal func killCmd(c *cliconfig.KillValues) error { - var ( - killFuncs []shared.ParallelWorkerInput - killSignal uint = uint(syscall.SIGTERM) - ) + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(Ctx, "killCmd") + defer span.Finish() + } + + // Check if the signalString provided by the user is valid + // Invalid signals will return err + killSignal, err := signal.ParseSignal(c.Signal) + if err != nil { + return err + } rootless.SetSkipStorageSetup(true) - 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) - if c.Signal != "" { - // Check if the signalString provided by the user is valid - // Invalid signals will return err - sysSignal, err := signal.ParseSignal(c.Signal) - if err != nil { - return err - } - killSignal = uint(sysSignal) - } - - containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") + ok, failures, err := runtime.KillContainers(getContext(), c, killSignal) if err != nil { - if len(containers) == 0 { - return err - } - fmt.Println(err.Error()) + return err } - for _, ctr := range containers { - con := ctr - f := func() error { - return con.Kill(killSignal) - } - - killFuncs = append(killFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) + for _, id := range ok { + fmt.Println(id) } - maxWorkers := shared.Parallelize("kill") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks - } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) + if len(failures) > 0 { + keys := reflect.ValueOf(failures).MapKeys() + lastKey := keys[len(keys)-1].String() + lastErr := failures[lastKey] + delete(failures, lastKey) - killErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, killFuncs) - return printParallelOutput(killErrors, errCount) + for _, err := range failures { + outputError(err) + } + return lastErr + } + return nil } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 2b96f0c20..3faea493c 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -86,6 +86,9 @@ func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, err if c.Flags().Changed("tmpdir") { options = append(options, libpod.WithTmpDir(c.GlobalFlags.TmpDir)) } + if c.Flags().Changed("network-cmd-path") { + options = append(options, libpod.WithNetworkCmdPath(c.GlobalFlags.NetworkCmdPath)) + } if c.Flags().Changed("cgroup-manager") { options = append(options, libpod.WithCgroupManager(c.GlobalFlags.CGroupManager)) diff --git a/cmd/podman/load.go b/cmd/podman/load.go index 5a0742aba..04ff9fcca 100644 --- a/cmd/podman/load.go +++ b/cmd/podman/load.go @@ -5,20 +5,24 @@ import ( "io" "io/ioutil" "os" + "strings" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) var ( loadCommand cliconfig.LoadValues - loadDescription = "Loads the image from docker-archive stored on the local machine." - _loadCommand = &cobra.Command{ - Use: "load [flags] [PATH]", - Short: "Load an image from docker archive", + loadDescription = "Loads an image from a locally stored archive (tar file) into container storage." + + _loadCommand = &cobra.Command{ + Use: "load [flags] [NAME[:TAG]]", + Short: "Load an image from container archive", Long: loadDescription, RunE: func(cmd *cobra.Command, args []string) error { loadCommand.InputArgs = args @@ -30,9 +34,10 @@ var ( func init() { loadCommand.Command = _loadCommand + loadCommand.SetHelpTemplate(HelpTemplate()) loadCommand.SetUsageTemplate(UsageTemplate()) flags := loadCommand.Flags() - flags.StringVarP(&loadCommand.Input, "input", "i", "/dev/stdin", "Read from archive file, default is STDIN") + flags.StringVarP(&loadCommand.Input, "input", "i", "", "Read from specified archive file (default: stdin)") flags.BoolVarP(&loadCommand.Quiet, "quiet", "q", false, "Suppress the output") flags.StringVar(&loadCommand.SignaturePolicy, "signature-policy", "", "Pathname of signature policy file (not usually used)") @@ -58,46 +63,43 @@ func loadCmd(c *cliconfig.LoadValues) error { } defer runtime.Shutdown(false) - input := c.Input - if runtime.Remote && len(input) == 0 { - return errors.New("the remote client requires you to load via -i and a tarball") - } - if input == "/dev/stdin" { - fi, err := os.Stdin.Stat() - if err != nil { + if len(c.Input) > 0 { + if err := parse.ValidateFileName(c.Input); err != nil { return err } - // checking if loading from pipe - if !fi.Mode().IsRegular() { - outFile, err := ioutil.TempFile("/var/tmp", "podman") - if err != nil { - return errors.Errorf("error creating file %v", err) - } - defer os.Remove(outFile.Name()) - defer outFile.Close() - - inFile, err := os.OpenFile(input, 0, 0666) - if err != nil { - return errors.Errorf("error reading file %v", err) - } - defer inFile.Close() - - _, err = io.Copy(outFile, inFile) - if err != nil { - return errors.Errorf("error copying file %v", err) - } + } else { + if terminal.IsTerminal(int(os.Stdin.Fd())) { + return errors.Errorf("cannot read from terminal. Use command-line redirection or the --input flag.") + } + outFile, err := ioutil.TempFile("/var/tmp", "podman") + if err != nil { + return errors.Errorf("error creating file %v", err) + } + defer os.Remove(outFile.Name()) + defer outFile.Close() - input = outFile.Name() + _, err = io.Copy(outFile, os.Stdin) + if err != nil { + return errors.Errorf("error copying file %v", err) } - } - if err := validateFileName(input); err != nil { - return err + + c.Input = outFile.Name() } names, err := runtime.LoadImage(getContext(), imageName, c) if err != nil { return err } + if len(imageName) > 0 { + split := strings.Split(names, ",") + newImage, err := runtime.NewImageFromLocal(split[0]) + if err != nil { + return err + } + if err := newImage.TagImage(imageName); err != nil { + return errors.Wrapf(err, "error adding '%s' to image %q", imageName, newImage.InputName) + } + } fmt.Println("Loaded image(s): " + names) return nil } diff --git a/cmd/podman/login.go b/cmd/podman/login.go index 48d4eefbc..4e96b43cb 100644 --- a/cmd/podman/login.go +++ b/cmd/podman/login.go @@ -37,6 +37,7 @@ var ( func init() { loginCommand.Command = _loginCommand + loginCommand.SetHelpTemplate(HelpTemplate()) loginCommand.SetUsageTemplate(UsageTemplate()) flags := loginCommand.Flags() @@ -44,7 +45,7 @@ func init() { flags.StringVar(&loginCommand.CertDir, "cert-dir", "", "Pathname of a directory containing TLS certificates and keys used to connect to the registry") flags.BoolVar(&loginCommand.GetLogin, "get-login", true, "Return the current login user for the registry") flags.StringVarP(&loginCommand.Password, "password", "p", "", "Password for registry") - flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&loginCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") flags.StringVarP(&loginCommand.Username, "username", "u", "", "Username for registry") flags.BoolVar(&loginCommand.StdinPassword, "password-stdin", false, "Take the password from stdin") diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go index 2a540ceba..268e6b44c 100644 --- a/cmd/podman/logout.go +++ b/cmd/podman/logout.go @@ -30,6 +30,7 @@ var ( func init() { logoutCommand.Command = _logoutCommand + logoutCommand.SetHelpTemplate(HelpTemplate()) logoutCommand.SetUsageTemplate(UsageTemplate()) flags := logoutCommand.Flags() flags.BoolVarP(&logoutCommand.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file") diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index a02010eda..a1b5fb4cc 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -1,24 +1,24 @@ package main import ( - "os" "time" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/logs" + "github.com/containers/libpod/pkg/adapter" + "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( logsCommand cliconfig.LogsValues - logsDescription = "The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. This does not guarantee execution" + - "order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs" + logsDescription = `Retrieves logs for one or more containers. + + This does not guarantee execution order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs. +` _logsCommand = &cobra.Command{ - Use: "logs [flags] CONTAINER", + Use: "logs [flags] CONTAINER [CONTAINER...]", Short: "Fetch the logs of a container", Long: logsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -26,14 +26,25 @@ var ( logsCommand.GlobalFlags = MainGlobalOpts return logsCmd(&logsCommand) }, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 && logsCommand.Latest { + return errors.New("no containers can be specified when using 'latest'") + } + if !logsCommand.Latest && len(args) < 1 { + return errors.New("specify at least one container name or ID to log") + } + return nil + }, Example: `podman logs ctrID podman logs --tail 2 mywebserver - podman logs --follow=true --since 10m ctrID`, + podman logs --follow=true --since 10m ctrID + podman logs mywebserver mydbserver`, } ) func init() { logsCommand.Command = _logsCommand + logsCommand.SetHelpTemplate(HelpTemplate()) logsCommand.SetUsageTemplate(UsageTemplate()) flags := logsCommand.Flags() flags.BoolVar(&logsCommand.Details, "details", false, "Show extra details provided to the logs") @@ -50,31 +61,25 @@ func init() { } func logsCmd(c *cliconfig.LogsValues) error { - var ctr *libpod.Container var err error - 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) - args := c.InputArgs - if len(args) != 1 && !c.Latest { - return errors.Errorf("'podman logs' requires exactly one container name/ID") - } - sinceTime := time.Time{} if c.Flag("since").Changed { // parse time, error out if something is wrong - since, err := parseInputTime(c.Since) + since, err := util.ParseInputTime(c.Since) if err != nil { return errors.Wrapf(err, "could not parse time: %q", c.Since) } sinceTime = since } - opts := &logs.LogOptions{ + opts := &libpod.LogOptions{ Details: c.Details, Follow: c.Follow, Since: sinceTime, @@ -82,52 +87,5 @@ func logsCmd(c *cliconfig.LogsValues) error { Timestamps: c.Timestamps, } - if c.Latest { - ctr, err = runtime.GetLatestContainer() - } else { - ctr, err = runtime.LookupContainer(args[0]) - } - if err != nil { - return err - } - - logPath := ctr.LogPath() - - state, err := ctr.State() - if err != nil { - return err - } - - // If the log file does not exist yet and the container is in the - // Configured state, it has never been started before and no logs exist - // Exit cleanly in this case - if _, err := os.Stat(logPath); err != nil { - if state == libpod.ContainerStateConfigured { - logrus.Debugf("Container has not been started, no logs exist yet") - return nil - } - } - return logs.ReadLogs(logPath, ctr, opts) -} - -// parseInputTime takes the users input and to determine if it is valid and -// returns a time format and error. The input is compared to known time formats -// or a duration which implies no-duration -func parseInputTime(inputTime string) (time.Time, error) { - timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999", - "2006-01-02Z07:00", "2006-01-02"} - // iterate the supported time formats - for _, tf := range timeFormats { - t, err := time.Parse(tf, inputTime) - if err == nil { - return t, nil - } - } - - // input might be a duration - duration, err := time.ParseDuration(inputTime) - if err != nil { - return time.Time{}, errors.Errorf("unable to interpret time value") - } - return time.Now().Add(-duration), nil + return runtime.Log(c, opts) } diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 98e2f23ca..dd8b61408 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -36,21 +36,25 @@ var ( // implemented. var mainCommands = []*cobra.Command{ _buildCommand, + _eventsCommand, _exportCommand, _historyCommand, &_imagesCommand, _importCommand, _infoCommand, - _inspectCommand, + &_inspectCommand, _killCommand, _loadCommand, + _logsCommand, podCommand.Command, _pullCommand, _pushCommand, &_rmiCommand, _saveCommand, + _stopCommand, _tagCommand, _versionCommand, + _waitCommand, imageCommand.Command, systemCommand.Command, } @@ -63,26 +67,32 @@ var cmdsNotRequiringRootless = map[*cobra.Command]bool{ _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, - _restartCommand: true, - _runCommand: true, - _unpauseCommand: true, - _searchCommand: true, - _statsCommand: true, - _stopCommand: true, - _topCommand: true, + _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", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, + RunE: commandRunE(), PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return before(cmd, args) }, @@ -104,11 +114,12 @@ func init() { rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Config, "config", "", "Path of a libpod config file detailing container server configuration options") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.ConmonPath, "conmon", "", "Path of the conmon binary") + rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.NetworkCmdPath, "network-cmd-path", "", "Path to the command for configuring the network") 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") 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 (default), fatal or panic") + 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") rootCmd.PersistentFlags().MarkHidden("max-workers") rootCmd.PersistentFlags().StringVar(&MainGlobalOpts.Namespace, "namespace", "", "Set the libpod namespace, used to create separate views of the containers and pods on the system") diff --git a/cmd/podman/mount.go b/cmd/podman/mount.go index 3a3432194..d074551ce 100644 --- a/cmd/podman/mount.go +++ b/cmd/podman/mount.go @@ -5,8 +5,8 @@ import ( "fmt" "os" + of "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - of "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" @@ -17,16 +17,15 @@ import ( var ( mountCommand cliconfig.MountValues - mountDescription = ` - podman mount - Lists all mounted containers mount points + mountDescription = `podman mount + Lists all mounted containers mount points if no container is specified - podman mount CONTAINER-NAME-OR-ID - Mounts the specified container and outputs the mountpoint + podman mount CONTAINER-NAME-OR-ID + Mounts the specified container and outputs the mountpoint ` _mountCommand = &cobra.Command{ - Use: "mount [flags] CONTAINER", + Use: "mount [flags] [CONTAINER]", Short: "Mount a working container's root filesystem", Long: mountDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -42,6 +41,7 @@ var ( func init() { mountCommand.Command = _mountCommand + mountCommand.SetHelpTemplate(HelpTemplate()) mountCommand.SetUsageTemplate(UsageTemplate()) flags := mountCommand.Flags() flags.BoolVarP(&mountCommand.All, "all", "a", false, "Mount all containers") diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 3a5b80359..fa4648128 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -14,12 +14,8 @@ import ( var ( pauseCommand cliconfig.PauseValues - pauseDescription = ` - podman pause - - Pauses one or more running containers. The container name or ID can be used. -` - _pauseCommand = &cobra.Command{ + pauseDescription = `Pauses one or more running containers. The container name or ID can be used.` + _pauseCommand = &cobra.Command{ Use: "pause [flags] CONTAINER [CONTAINER...]", Short: "Pause all the processes in one or more containers", Long: pauseDescription, @@ -36,6 +32,7 @@ var ( func init() { pauseCommand.Command = _pauseCommand + pauseCommand.SetHelpTemplate(HelpTemplate()) pauseCommand.SetUsageTemplate(UsageTemplate()) flags := pauseCommand.Flags() flags.BoolVarP(&pauseCommand.All, "all", "a", false, "Pause all running containers") diff --git a/cmd/podman/play.go b/cmd/podman/play.go index 495a1f170..95eae653e 100644 --- a/cmd/podman/play.go +++ b/cmd/podman/play.go @@ -12,11 +12,13 @@ var ( Use: "play", Short: "Play a pod", Long: playDescription, + RunE: commandRunE(), } ) func init() { playCommand.Command = _playCommand + playCommand.SetHelpTemplate(HelpTemplate()) playCommand.SetUsageTemplate(UsageTemplate()) playCommand.AddCommand(getPlaySubCommands()...) } diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 6f23e340e..10221a339 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "io" "io/ioutil" @@ -25,10 +26,17 @@ import ( "k8s.io/api/core/v1" ) +const ( + // https://kubernetes.io/docs/concepts/storage/volumes/#hostpath + createDirectoryPermission = 0755 +) + var ( playKubeCommand cliconfig.KubePlayValues - playKubeDescription = "Play a Pod and its containers based on a Kubrernetes YAML" - _playKubeCommand = &cobra.Command{ + playKubeDescription = `Command reads in a structured file of Kubernetes YAML. + + It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.` + _playKubeCommand = &cobra.Command{ Use: "kube [flags] KUBEFILE", Short: "Play a pod based on Kubernetes YAML", Long: playKubeDescription, @@ -44,6 +52,7 @@ var ( func init() { playKubeCommand.Command = _playKubeCommand + playKubeCommand.SetHelpTemplate(HelpTemplate()) playKubeCommand.SetUsageTemplate(UsageTemplate()) flags := playKubeCommand.Flags() flags.StringVar(&playKubeCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") @@ -51,7 +60,7 @@ func init() { flags.StringVar(&playKubeCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { @@ -90,11 +99,20 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { return errors.Wrapf(err, "unable to read %s as YAML", args[0]) } + // check for name collision between pod and container + podName := podYAML.ObjectMeta.Name + for _, n := range podYAML.Spec.Containers { + if n.Name == podName { + fmt.Printf("a container exists with the same name (%s) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName) + podName = fmt.Sprintf("%s_pod", podName) + } + } + podOptions = append(podOptions, libpod.WithInfraContainer()) - podOptions = append(podOptions, libpod.WithPodName(podYAML.ObjectMeta.Name)) + podOptions = append(podOptions, libpod.WithPodName(podName)) // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml - nsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + nsOptions, err := shared.GetNamespaceOptions(strings.Split(shared.DefaultKernelNamespaces, ",")) if err != nil { return err } @@ -135,16 +153,45 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify) } + // map from name to mount point + volumes := make(map[string]string) + for _, volume := range podYAML.Spec.Volumes { + hostPath := volume.VolumeSource.HostPath + if hostPath == nil { + return errors.Errorf("HostPath is currently the only supported VolumeSource") + } + if hostPath.Type != nil { + switch *hostPath.Type { + case v1.HostPathDirectoryOrCreate: + if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) { + if err := os.Mkdir(hostPath.Path, createDirectoryPermission); err != nil { + return errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path) + } + } + case v1.HostPathDirectory: + // do nothing here because we will verify the path exists in validateVolumeHostDir + break + default: + return errors.Errorf("Directories are the only supported HostPath type") + } + } + if err := shared.ValidateVolumeHostDir(hostPath.Path); err != nil { + return errors.Wrapf(err, "Error in parsing HostPath in YAML") + } + fmt.Println(volume.Name) + volumes[volume.Name] = hostPath.Path + } + for _, container := range podYAML.Spec.Containers { newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image2.SigningOptions{}, false, nil) if err != nil { return err } - createConfig := kubeContainerToCreateConfig(container, runtime, newImage, namespaces) + createConfig, err := kubeContainerToCreateConfig(ctx, container, runtime, newImage, namespaces, volumes) if err != nil { return err } - ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, pod) + ctr, err := shared.CreateContainerFromCreateConfig(runtime, createConfig, ctx, pod) if err != nil { return err } @@ -185,7 +232,7 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { } // kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container -func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Runtime, newImage *image2.Image, namespaces map[string]string) *createconfig.CreateConfig { +func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image2.Image, namespaces map[string]string, volumes map[string]string) (*createconfig.CreateConfig, error) { var ( containerConfig createconfig.CreateConfig envs map[string]string @@ -197,15 +244,25 @@ func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Run containerConfig.Name = containerYAML.Name containerConfig.Tty = containerYAML.TTY containerConfig.WorkDir = containerYAML.WorkingDir - if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { - containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem - } - if containerYAML.SecurityContext.Privileged != nil { - containerConfig.Privileged = *containerYAML.SecurityContext.Privileged + + imageData, _ := newImage.Inspect(ctx) + + containerConfig.User = "0" + if imageData != nil { + containerConfig.User = imageData.Config.User } - if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { - containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + if containerConfig.SecurityOpts != nil { + if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { + containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem + } + if containerYAML.SecurityContext.Privileged != nil { + containerConfig.Privileged = *containerYAML.SecurityContext.Privileged + } + + if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { + containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + } } containerConfig.Command = containerYAML.Command @@ -222,7 +279,9 @@ func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Run // disabled in code review per mheon //containerConfig.PidMode = ns.PidMode(namespaces["pid"]) containerConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) - + if len(containerConfig.WorkDir) == 0 { + containerConfig.WorkDir = "/" + } if len(containerYAML.Env) > 0 { envs = make(map[string]string) } @@ -231,5 +290,16 @@ func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Run envs[e.Name] = e.Value } containerConfig.Env = envs - return &containerConfig + + for _, volume := range containerYAML.VolumeMounts { + host_path, exists := volumes[volume.Name] + if !exists { + return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) + } + if err := shared.ValidateVolumeCtrDir(volume.MountPath); err != nil { + return nil, errors.Wrapf(err, "error in parsing MountPath") + } + containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", host_path, volume.MountPath)) + } + return &containerConfig, nil } diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index c1350bd4d..9a9c7a702 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -1,20 +1,24 @@ 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" ) var ( - podDescription = `Manage container pods. - -Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.` + podDescription = `Pods are a group of one or more containers sharing the same network, pid and ipc namespaces.` ) var podCommand = cliconfig.PodmanCommand{ Command: &cobra.Command{ Use: "pod", Short: "Manage pods", Long: podDescription, + RunE: commandRunE(), }, } @@ -29,12 +33,56 @@ var podSubCommands = []*cobra.Command{ _podRestartCommand, _podRmCommand, _podStartCommand, + _podStatsCommand, _podStopCommand, + _podTopCommand, _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.AddCommand(getPodSubCommands()...) + podCommand.SetHelpTemplate(HelpTemplate()) podCommand.SetUsageTemplate(UsageTemplate()) } diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index f1bbecb84..2f7a6b415 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -5,6 +5,7 @@ import ( "os" "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" "github.com/pkg/errors" @@ -14,16 +15,16 @@ import ( var ( // Kernel namespaces shared by default within a pod - DefaultKernelNamespaces = "cgroup,ipc,net,uts" - podCreateCommand cliconfig.PodCreateValues - podCreateDescription = "Creates a new empty pod. The pod ID is then" + - " printed to stdout. You can then start it at any time with the" + - " podman pod start <pod_id> command. The pod will be created with the" + - " initial state 'created'." + podCreateCommand cliconfig.PodCreateValues + + podCreateDescription = `After creating the pod, the pod ID is printed to stdout. + + You can then start it at any time with the podman pod start <pod_id> command. The pod will be created with the initial state 'created'.` _podCreateCommand = &cobra.Command{ Use: "create", + Args: noSubArgs, Short: "Create a new empty pod", Long: podCreateDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -36,6 +37,7 @@ var ( func init() { podCreateCommand.Command = _podCreateCommand + podCreateCommand.SetHelpTemplate(HelpTemplate()) podCreateCommand.SetUsageTemplate(UsageTemplate()) flags := podCreateCommand.Flags() flags.SetInterspersed(false) @@ -49,7 +51,7 @@ func init() { flags.StringVarP(&podCreateCommand.Name, "name", "n", "", "Assign a name to the pod") flags.StringVar(&podCreateCommand.PodIDFile, "pod-id-file", "", "Write the pod ID to the file") flags.StringSliceVarP(&podCreateCommand.Publish, "publish", "p", []string{}, "Publish a container's port, or a range of ports, to the host (default [])") - flags.StringVar(&podCreateCommand.Share, "share", DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") + flags.StringVar(&podCreateCommand.Share, "share", shared.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") } @@ -59,9 +61,6 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error { podIdFile *os.File ) - if len(c.InputArgs) > 0 { - return errors.New("podman pod create does not accept any arguments") - } runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") @@ -89,7 +88,7 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error { defer podIdFile.Sync() } - labels, err := getAllLabels(c.LabelFile, c.Labels) + labels, err := shared.GetAllLabels(c.LabelFile, c.Labels) if err != nil { return errors.Wrapf(err, "unable to process labels") } diff --git a/cmd/podman/pod_inspect.go b/cmd/podman/pod_inspect.go index 8b2747af0..851f39aa0 100644 --- a/cmd/podman/pod_inspect.go +++ b/cmd/podman/pod_inspect.go @@ -12,8 +12,11 @@ import ( var ( podInspectCommand cliconfig.PodInspectValues - podInspectDescription = "Display the configuration for a pod by name or id" - _podInspectCommand = &cobra.Command{ + podInspectDescription = `Display the configuration for a pod by name or id + + By default, this will render all results in a JSON array.` + + _podInspectCommand = &cobra.Command{ Use: "inspect [flags] POD", Short: "Displays a pod configuration", Long: podInspectDescription, @@ -28,9 +31,10 @@ var ( func init() { podInspectCommand.Command = _podInspectCommand + podInspectCommand.SetHelpTemplate(HelpTemplate()) podInspectCommand.SetUsageTemplate(UsageTemplate()) flags := podInspectCommand.Flags() - flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVarP(&podInspectCommand.Latest, "latest", "l", false, "Act on the latest pod podman is aware of") markFlagHiddenForRemoteClient("latest", flags) } @@ -40,6 +44,11 @@ func podInspectCmd(c *cliconfig.PodInspectValues) error { pod *adapter.Pod ) args := c.InputArgs + + if len(args) < 1 && !c.Latest { + return errors.Errorf("you must provide the name or id of a pod") + } + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") diff --git a/cmd/podman/pod_kill.go b/cmd/podman/pod_kill.go index 70d86d186..c538674a4 100644 --- a/cmd/podman/pod_kill.go +++ b/cmd/podman/pod_kill.go @@ -6,6 +6,7 @@ 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" @@ -14,8 +15,10 @@ import ( var ( podKillCommand cliconfig.PodKillValues - podKillDescription = "The main process of each container inside the specified pod will be sent SIGKILL, or any signal specified with option --signal." - _podKillCommand = &cobra.Command{ + podKillDescription = `Signals are sent to the main process of each container inside the specified pod. + + The default signal is SIGKILL, or any signal specified with option --signal.` + _podKillCommand = &cobra.Command{ Use: "kill [flags] POD [POD...]", Short: "Send the specified signal or SIGKILL to containers in pod", Long: podKillDescription, @@ -35,6 +38,7 @@ var ( func init() { podKillCommand.Command = _podKillCommand + podKillCommand.SetHelpTemplate(HelpTemplate()) podKillCommand.SetUsageTemplate(UsageTemplate()) flags := podKillCommand.Flags() flags.BoolVarP(&podKillCommand.All, "all", "a", false, "Kill all containers in all pods") @@ -45,6 +49,7 @@ 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_pause.go b/cmd/podman/pod_pause.go index f7c90dbbe..ff29e0e1d 100644 --- a/cmd/podman/pod_pause.go +++ b/cmd/podman/pod_pause.go @@ -11,8 +11,10 @@ import ( var ( podPauseCommand cliconfig.PodPauseValues - podPauseDescription = `Pauses one or more pods. The pod name or ID can be used.` - _podPauseCommand = &cobra.Command{ + podPauseDescription = `The pod name or ID can be used. + + All running containers within each specified pod will then be paused.` + _podPauseCommand = &cobra.Command{ Use: "pause [flags] POD [POD...]", Short: "Pause one or more pods", Long: podPauseDescription, @@ -32,6 +34,7 @@ var ( func init() { podPauseCommand.Command = _podPauseCommand + podPauseCommand.SetHelpTemplate(HelpTemplate()) podPauseCommand.SetUsageTemplate(UsageTemplate()) flags := podPauseCommand.Flags() flags.BoolVarP(&podPauseCommand.All, "all", "a", false, "Pause all running pods") diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index 70e077651..a956882cf 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -8,8 +8,8 @@ import ( "strings" "time" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" @@ -121,6 +121,7 @@ var ( _podPsCommand = &cobra.Command{ Use: "ps", Aliases: []string{"ls", "list"}, + Args: noSubArgs, Short: "List pods", Long: podPsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -133,6 +134,7 @@ var ( func init() { podPsCommand.Command = _podPsCommand + podPsCommand.SetHelpTemplate(HelpTemplate()) podPsCommand.SetUsageTemplate(UsageTemplate()) flags := podPsCommand.Flags() flags.BoolVar(&podPsCommand.CtrNames, "ctr-names", false, "Display the container names") @@ -160,10 +162,6 @@ func podPsCmd(c *cliconfig.PodPsValues) error { } defer runtime.Shutdown(false) - if len(c.InputArgs) > 0 { - return errors.Errorf("too many arguments, ps takes no arguments") - } - opts := podPsOptions{ NoTrunc: c.NoTrunc, Quiet: c.Quiet, diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index ba77e1409..9c8d28424 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -2,9 +2,11 @@ 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" @@ -12,8 +14,10 @@ import ( var ( podRestartCommand cliconfig.PodRestartValues - podRestartDescription = `Restarts one or more pods. The pod ID or name can be used.` - _podRestartCommand = &cobra.Command{ + podRestartDescription = `The pod ID or name can be used. + + All of the containers within each of the specified pods will be restarted. If a container in a pod is not currently running it will be started.` + _podRestartCommand = &cobra.Command{ Use: "restart [flags] POD [POD...]", Short: "Restart one or more pods", Long: podRestartDescription, @@ -33,6 +37,7 @@ var ( func init() { podRestartCommand.Command = _podRestartCommand + podRestartCommand.SetHelpTemplate(HelpTemplate()) podRestartCommand.SetUsageTemplate(UsageTemplate()) flags := podRestartCommand.Flags() flags.BoolVarP(&podRestartCommand.All, "all", "a", false, "Restart all running pods") @@ -43,12 +48,24 @@ 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 fa452b061..735676f8a 100644 --- a/cmd/podman/pod_rm.go +++ b/cmd/podman/pod_rm.go @@ -2,9 +2,11 @@ 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" @@ -12,11 +14,9 @@ import ( var ( podRmCommand cliconfig.PodRmValues - podRmDescription = fmt.Sprintf(` -podman rm will remove one or more pods from the host. The pod name or ID can -be used. A pod with containers will not be removed without --force. -If --force is specified, all containers will be stopped, then removed. -`) + podRmDescription = fmt.Sprintf(`podman rm will remove one or more pods from the host. + + The pod name or ID can be used. A pod with containers will not be removed without --force. If --force is specified, all containers will be stopped, then removed.`) _podRmCommand = &cobra.Command{ Use: "rm [flags] POD [POD...]", Short: "Remove one or more pods", @@ -37,6 +37,7 @@ If --force is specified, all containers will be stopped, then removed. func init() { podRmCommand.Command = _podRmCommand + podRmCommand.SetHelpTemplate(HelpTemplate()) podRmCommand.SetUsageTemplate(UsageTemplate()) flags := podRmCommand.Flags() flags.BoolVarP(&podRmCommand.All, "all", "a", false, "Remove all running pods") @@ -47,11 +48,23 @@ 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_start.go b/cmd/podman/pod_start.go index eef9d2a71..949af80d8 100644 --- a/cmd/podman/pod_start.go +++ b/cmd/podman/pod_start.go @@ -12,13 +12,11 @@ import ( var ( podStartCommand cliconfig.PodStartValues - podStartDescription = ` - podman pod start + podStartDescription = `The pod name or ID can be used. - Starts one or more pods. The pod name or ID can be used. -` + All containers defined in the pod will be started.` _podStartCommand = &cobra.Command{ - Use: "start POD [POD...]", + Use: "start [flags] POD [POD...]", Short: "Start one or more pods", Long: podStartDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -37,6 +35,7 @@ var ( func init() { podStartCommand.Command = _podStartCommand + podStartCommand.SetHelpTemplate(HelpTemplate()) podStartCommand.SetUsageTemplate(UsageTemplate()) flags := podStartCommand.Flags() flags.BoolVarP(&podStartCommand.All, "all", "a", false, "Start all pods") diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index f5edd21f8..e8ff322ce 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -11,10 +11,10 @@ import ( "encoding/json" tm "github.com/buger/goterm" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" - "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" "github.com/ulule/deepcopier" @@ -22,10 +22,11 @@ import ( var ( podStatsCommand cliconfig.PodStatsValues - podStatsDescription = "Display a live stream of resource usage statistics for the containers in or more pods" - _podStatsCommand = &cobra.Command{ - Use: "stats [flags] POD [POD...]", - Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one or more pods", + podStatsDescription = `For each specified pod this command will display percentage of CPU, memory, network I/O, block I/O and PIDs for containers in one the pods.` + + _podStatsCommand = &cobra.Command{ + Use: "stats [flags] [POD...]", + Short: "Display a live stream of resource usage statistics for the containers in one or more pods", Long: podStatsDescription, RunE: func(cmd *cobra.Command, args []string) error { podStatsCommand.InputArgs = args @@ -40,6 +41,7 @@ var ( func init() { podStatsCommand.Command = _podStatsCommand + podStatsCommand.SetHelpTemplate(HelpTemplate()) podStatsCommand.SetUsageTemplate(UsageTemplate()) flags := podStatsCommand.Flags() flags.BoolVarP(&podStatsCommand.All, "all", "a", false, "Provide stats for all running pods") @@ -51,9 +53,10 @@ func init() { } func podStatsCmd(c *cliconfig.PodStatsValues) error { - var ( - podFunc func() ([]*libpod.Pod, error) - ) + + if os.Geteuid() != 0 { + return errors.New("stats is not supported in rootless mode") + } format := c.Format all := c.All @@ -76,7 +79,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { all = true } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -87,29 +90,12 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { times = 1 } - if len(c.InputArgs) > 0 { - podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.InputArgs, runtime) } - } else if latest { - podFunc = func() ([]*libpod.Pod, error) { - latestPod, err := runtime.GetLatestPod() - if err != nil { - return nil, err - } - return []*libpod.Pod{latestPod}, err - } - } else if all { - podFunc = runtime.GetAllPods - } else { - podFunc = runtime.GetRunningPods - } - - pods, err := podFunc() + pods, err := runtime.GetStatPods(c) if err != nil { return errors.Wrapf(err, "unable to get a list of pods") } - // First we need to get an initial pass of pod/ctr stats (these are not printed) - var podStats []*libpod.PodContainerStats + var podStats []*adapter.PodContainerStats for _, p := range pods { cons, err := p.AllContainersByID() if err != nil { @@ -120,7 +106,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { for _, c := range cons { emptyStats[c] = &libpod.ContainerStats{} } - ps := libpod.PodContainerStats{ + ps := adapter.PodContainerStats{ Pod: p, ContainerStats: emptyStats, } @@ -128,10 +114,10 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { } // Create empty container stat results for our first pass - var previousPodStats []*libpod.PodContainerStats + var previousPodStats []*adapter.PodContainerStats for _, p := range pods { cs := make(map[string]*libpod.ContainerStats) - pcs := libpod.PodContainerStats{ + pcs := adapter.PodContainerStats{ Pod: p, ContainerStats: cs, } @@ -164,7 +150,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { } for i := 0; i < times; i += step { - var newStats []*libpod.PodContainerStats + var newStats []*adapter.PodContainerStats for _, p := range pods { prevStat := getPreviousPodContainerStats(p.ID(), previousPodStats) newPodStats, err := p.GetPodStats(prevStat) @@ -174,7 +160,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { if err != nil { return err } - newPod := libpod.PodContainerStats{ + newPod := adapter.PodContainerStats{ Pod: p, ContainerStats: newPodStats, } @@ -202,7 +188,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { time.Sleep(time.Second) previousPodStats := new([]*libpod.PodContainerStats) deepcopier.Copy(newStats).To(previousPodStats) - pods, err = podFunc() + pods, err = runtime.GetStatPods(c) if err != nil { return err } @@ -211,7 +197,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { return nil } -func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut { +func podContainerStatsToPodStatOut(stats []*adapter.PodContainerStats) []*podStatOut { var out []*podStatOut for _, p := range stats { for _, c := range p.ContainerStats { @@ -295,7 +281,7 @@ func outputToStdOut(stats []*podStatOut) { w.Flush() } -func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats { +func getPreviousPodContainerStats(podID string, prev []*adapter.PodContainerStats) map[string]*libpod.ContainerStats { for _, p := range prev { if podID == p.Pod.ID() { return p.ContainerStats @@ -304,7 +290,7 @@ func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats return map[string]*libpod.ContainerStats{} } -func outputJson(stats []*libpod.PodContainerStats) error { +func outputJson(stats []*adapter.PodContainerStats) error { b, err := json.MarshalIndent(&stats, "", " ") if err != nil { return err diff --git a/cmd/podman/pod_stop.go b/cmd/podman/pod_stop.go index 951cf082a..754a3a7db 100644 --- a/cmd/podman/pod_stop.go +++ b/cmd/podman/pod_stop.go @@ -2,9 +2,11 @@ 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" @@ -12,11 +14,9 @@ import ( var ( podStopCommand cliconfig.PodStopValues - podStopDescription = ` - podman pod stop + podStopDescription = `The pod name or ID can be used. - Stops one or more running pods. The pod name or ID can be used. -` + This command will stop all running containers in each of the specified pods.` _podStopCommand = &cobra.Command{ Use: "stop [flags] POD [POD...]", @@ -38,6 +38,7 @@ var ( func init() { podStopCommand.Command = _podStopCommand + podStopCommand.SetHelpTemplate(HelpTemplate()) podStopCommand.SetUsageTemplate(UsageTemplate()) flags := podStopCommand.Flags() flags.BoolVarP(&podStopCommand.All, "all", "a", false, "Stop all running pods") @@ -47,12 +48,24 @@ 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 6a26e3dff..f65d66df6 100644 --- a/cmd/podman/pod_top.go +++ b/cmd/podman/pod_top.go @@ -2,14 +2,14 @@ package main import ( "fmt" + "github.com/containers/libpod/pkg/adapter" "os" "strings" "text/tabwriter" "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/spf13/cobra" ) @@ -17,12 +17,10 @@ import ( var ( podTopCommand cliconfig.PodTopValues - podTopDescription = fmt.Sprintf(`Display the running processes containers in a pod. Specify format descriptors -to alter the output. You may run "podman pod top -l pid pcpu seccomp" to print -the process ID, the CPU percentage and the seccomp mode of each process of -the latest pod. -%s -`, getDescriptorString()) + podTopDescription = fmt.Sprintf(`Specify format descriptors to alter the output. + + You may run "podman pod top -l pid pcpu seccomp" to print the process ID, the CPU percentage and the seccomp mode of each process of the latest pod. +%s`, getDescriptorString()) _podTopCommand = &cobra.Command{ Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS]", @@ -41,6 +39,7 @@ the latest pod. func init() { podTopCommand.Command = _podTopCommand + podTopCommand.SetHelpTemplate(HelpTemplate()) podTopCommand.SetUsageTemplate(UsageTemplate()) flags := podTopCommand.Flags() flags.BoolVarP(&podTopCommand.Latest, "latest,", "l", false, "Act on the latest pod podman is aware of") @@ -50,10 +49,15 @@ func init() { } func podTopCmd(c *cliconfig.PodTopValues) error { - var pod *libpod.Pod - var err error + var ( + descriptors []string + ) args := c.InputArgs + if os.Geteuid() != 0 { + rootless.SetSkipStorageSetup(true) + } + if c.ListDescriptors { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err != nil { @@ -67,39 +71,43 @@ func podTopCmd(c *cliconfig.PodTopValues) error { return errors.Errorf("you must provide the name or id of a running pod") } - 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) - var descriptors []string if c.Latest { descriptors = args - pod, err = runtime.GetLatestPod() } else { descriptors = args[1:] - pod, err = runtime.LookupPod(args[0]) - } - - if err != nil { - return errors.Wrapf(err, "unable to lookup requested container") } - podStatus, err := shared.GetPodStatus(pod) - if err != nil { - return err - } - if podStatus != "Running" { - return errors.Errorf("pod top can only be used on pods with at least one running container") + 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) + } } - psOutput, err := pod.GetPodPidInformation(descriptors) + w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) + psOutput, err := runtime.PodTop(c, descriptors) if err != nil { return err } - - w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0) for _, proc := range psOutput { fmt.Fprintln(w, proc) } diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go index 6b142d573..0623c6abb 100644 --- a/cmd/podman/pod_unpause.go +++ b/cmd/podman/pod_unpause.go @@ -12,8 +12,10 @@ import ( var ( podUnpauseCommand cliconfig.PodUnpauseValues - podUnpauseDescription = `Unpauses one or more pods. The pod name or ID can be used.` - _podUnpauseCommand = &cobra.Command{ + podUnpauseDescription = `The podman unpause command will unpause all "paused" containers assigned to the pod. + + The pod name or ID can be used.` + _podUnpauseCommand = &cobra.Command{ Use: "unpause [flags] POD [POD...]", Short: "Unpause one or more pods", Long: podUnpauseDescription, @@ -33,6 +35,7 @@ var ( func init() { podUnpauseCommand.Command = _podUnpauseCommand + podUnpauseCommand.SetHelpTemplate(HelpTemplate()) podUnpauseCommand.SetUsageTemplate(UsageTemplate()) flags := podUnpauseCommand.Flags() flags.BoolVarP(&podUnpauseCommand.All, "all", "a", false, "Unpause all running pods") diff --git a/cmd/podman/port.go b/cmd/podman/port.go index ffb5749fb..b5a4d3eec 100644 --- a/cmd/podman/port.go +++ b/cmd/podman/port.go @@ -14,10 +14,7 @@ import ( var ( portCommand cliconfig.PortValues - portDescription = ` - podman port - - List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT + portDescription = `List port mappings for the CONTAINER, or lookup the public-facing port that is NAT-ed to the PRIVATE_PORT ` _portCommand = &cobra.Command{ Use: "port [flags] CONTAINER", @@ -39,6 +36,7 @@ var ( func init() { portCommand.Command = _portCommand + portCommand.SetHelpTemplate(HelpTemplate()) portCommand.SetUsageTemplate(UsageTemplate()) flags := portCommand.Flags() diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index fe4173fdd..27774f95d 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -12,11 +12,12 @@ import ( "text/tabwriter" "time" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "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" @@ -159,6 +160,7 @@ var ( psDescription = "Prints out information about the containers" _psCommand = cobra.Command{ Use: "ps", + Args: noSubArgs, Short: "List containers", Long: psDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -172,28 +174,36 @@ var ( } ) -func init() { - psCommand.Command = &_psCommand - psCommand.SetUsageTemplate(UsageTemplate()) - flags := psCommand.Flags() - flags.BoolVarP(&psCommand.All, "all", "a", false, "Show all the containers, default is only running containers") - flags.StringSliceVarP(&psCommand.Filter, "filter", "f", []string{}, "Filter output based on conditions given") - flags.StringVar(&psCommand.Format, "format", "", "Pretty-print containers to JSON or using a Go template") - flags.IntVarP(&psCommand.Last, "last", "n", -1, "Print the n last created containers (all states)") - flags.BoolVarP(&psCommand.Latest, "latest", "l", false, "Show the latest container created (all states)") - flags.BoolVar(&psCommand.Namespace, "namespace", false, "Display namespace information") - flags.BoolVar(&psCommand.Namespace, "ns", false, "Display namespace information") - flags.BoolVar(&psCommand.NoTrunct, "no-trunc", false, "Display the extended information") - flags.BoolVarP(&psCommand.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") - flags.BoolVarP(&psCommand.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") - flags.BoolVarP(&psCommand.Size, "size", "s", false, "Display the total file sizes") - flags.StringVar(&psCommand.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") - flags.BoolVar(&psCommand.Sync, "sync", false, "Sync container state with OCI runtime") +func psInit(command *cliconfig.PsValues) { + command.SetHelpTemplate(HelpTemplate()) + command.SetUsageTemplate(UsageTemplate()) + flags := command.Flags() + flags.BoolVarP(&command.All, "all", "a", false, "Show all the containers, default is only running containers") + flags.StringSliceVarP(&command.Filter, "filter", "f", []string{}, "Filter output based on conditions given") + flags.StringVar(&command.Format, "format", "", "Pretty-print containers to JSON or using a Go template") + flags.IntVarP(&command.Last, "last", "n", -1, "Print the n last created containers (all states)") + flags.BoolVarP(&command.Latest, "latest", "l", false, "Show the latest container created (all states)") + flags.BoolVar(&command.Namespace, "namespace", false, "Display namespace information") + flags.BoolVar(&command.Namespace, "ns", false, "Display namespace information") + flags.BoolVar(&command.NoTrunct, "no-trunc", false, "Display the extended information") + flags.BoolVarP(&command.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with") + flags.BoolVarP(&command.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only") + flags.BoolVarP(&command.Size, "size", "s", false, "Display the total file sizes") + flags.StringVar(&command.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status") + flags.BoolVar(&command.Sync, "sync", false, "Sync container state with OCI runtime") markFlagHiddenForRemoteClient("latest", flags) } +func init() { + psCommand.Command = &_psCommand + psInit(&psCommand) +} + 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() @@ -215,10 +225,6 @@ func psCmd(c *cliconfig.PsValues) error { defer runtime.Shutdown(false) - if len(c.InputArgs) > 0 { - return errors.Errorf("too many arguments, ps takes no arguments") - } - opts := shared.PsOptions{ All: c.All, Format: c.Format, @@ -417,7 +423,7 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru return false }, nil case "status": - if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { + if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { return nil, errors.Errorf("%s is not a valid status", filterValue) } return func(c *libpod.Container) bool { @@ -425,9 +431,14 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru if err != nil { return false } + if filterValue == "stopped" { + filterValue = "exited" + } state := status.String() if status == libpod.ContainerStateConfigured { state = "created" + } else if status == libpod.ContainerStateStopped { + state = "exited" } return state == filterValue }, nil @@ -483,6 +494,14 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru } return false }, nil + case "health": + return func(c *libpod.Container) bool { + hcStatus, err := c.HealthCheckStatus() + if err != nil { + return false + } + return hcStatus == filterValue + }, nil } return nil, errors.Errorf("%s is an invalid filter", filter) } diff --git a/cmd/podman/pull.go b/cmd/podman/pull.go index 5f4658fe1..8888c5e28 100644 --- a/cmd/podman/pull.go +++ b/cmd/podman/pull.go @@ -23,11 +23,9 @@ import ( var ( pullCommand cliconfig.PullValues - pullDescription = ` -Pulls an image from a registry and stores it locally. -An image can be pulled using its tag or digest. If a tag is not -specified, the image with the 'latest' tag (if it exists) is pulled -` + pullDescription = `Pulls an image from a registry and stores it locally. + + An image can be pulled using its tag or digest. If a tag is not specified, the image with the 'latest' tag (if it exists) is pulled.` _pullCommand = &cobra.Command{ Use: "pull [flags] IMAGE-PATH", Short: "Pull an image from a registry", @@ -45,6 +43,7 @@ specified, the image with the 'latest' tag (if it exists) is pulled func init() { pullCommand.Command = _pullCommand + pullCommand.SetHelpTemplate(HelpTemplate()) pullCommand.SetUsageTemplate(UsageTemplate()) flags := pullCommand.Flags() flags.BoolVar(&pullCommand.AllTags, "all-tags", false, "All tagged images inthe repository will be pulled") @@ -53,7 +52,7 @@ func init() { flags.StringVar(&pullCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVarP(&pullCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&pullCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&pullCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } diff --git a/cmd/podman/push.go b/cmd/podman/push.go index bc909cb5e..a1dac24ae 100644 --- a/cmd/podman/push.go +++ b/cmd/podman/push.go @@ -20,10 +20,9 @@ import ( var ( pushCommand cliconfig.PushValues - pushDescription = fmt.Sprintf(` - Pushes an image to a specified location. - The Image "DESTINATION" uses a "transport":"details" format. - See podman-push(1) section "DESTINATION" for the expected format`) + pushDescription = fmt.Sprintf(`Pushes an image to a specified location. + + The Image "DESTINATION" uses a "transport":"details" format. See podman-push(1) section "DESTINATION" for the expected format.`) _pushCommand = &cobra.Command{ Use: "push [flags] IMAGE REGISTRY", @@ -42,6 +41,7 @@ var ( func init() { pushCommand.Command = _pushCommand + pushCommand.SetHelpTemplate(HelpTemplate()) pushCommand.SetUsageTemplate(UsageTemplate()) flags := pushCommand.Flags() flags.MarkHidden("signature-policy") @@ -54,7 +54,7 @@ func init() { flags.BoolVar(&pushCommand.RemoveSignatures, "remove-signatures", false, "Discard any pre-existing signatures in the image") flags.StringVar(&pushCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.StringVar(&pushCommand.SignBy, "sign-by", "", "Add a signature at the destination using the specified key") - flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&pushCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func pushCmd(c *cliconfig.PushValues) error { diff --git a/cmd/podman/refresh.go b/cmd/podman/refresh.go index 193be6953..ed2e173ab 100644 --- a/cmd/podman/refresh.go +++ b/cmd/podman/refresh.go @@ -12,9 +12,13 @@ import ( var ( refreshCommand cliconfig.RefreshValues - refreshDescription = "The refresh command resets the state of all containers to handle database changes after a Podman upgrade. All running containers will be restarted." - _refreshCommand = &cobra.Command{ + refreshDescription = `Resets the state of all containers to handle database changes after a Podman upgrade. + + All running containers will be restarted. +` + _refreshCommand = &cobra.Command{ Use: "refresh", + Args: noSubArgs, Short: "Refresh container state", Long: refreshDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -28,14 +32,11 @@ var ( func init() { _refreshCommand.Hidden = true refreshCommand.Command = _refreshCommand + refreshCommand.SetHelpTemplate(HelpTemplate()) refreshCommand.SetUsageTemplate(UsageTemplate()) } func refreshCmd(c *cliconfig.RefreshValues) error { - if len(c.InputArgs) > 0 { - return errors.Errorf("refresh does not accept any arguments") - } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") diff --git a/cmd/podman/restart.go b/cmd/podman/restart.go index 5aa12070e..e6a6d8434 100644 --- a/cmd/podman/restart.go +++ b/cmd/podman/restart.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os" "github.com/containers/libpod/cmd/podman/cliconfig" @@ -16,8 +15,10 @@ import ( var ( restartCommand cliconfig.RestartValues - restartDescription = `Restarts one or more running containers. The container ID or name can be used. A timeout before forcibly stopping can be set, but defaults to 10 seconds` - _restartCommand = &cobra.Command{ + restartDescription = `Restarts one or more running containers. The container ID or name can be used. + + A timeout before forcibly stopping can be set, but defaults to 10 seconds.` + _restartCommand = &cobra.Command{ Use: "restart [flags] CONTAINER [CONTAINER...]", Short: "Restart one or more containers", Long: restartDescription, @@ -37,6 +38,7 @@ var ( func init() { restartCommand.Command = _restartCommand + restartCommand.SetHelpTemplate(HelpTemplate()) restartCommand.SetUsageTemplate(UsageTemplate()) flags := restartCommand.Flags() flags.BoolVarP(&restartCommand.All, "all", "a", false, "Restart all non-running containers") @@ -58,6 +60,15 @@ func restartCmd(c *cliconfig.RestartValues) error { 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 @@ -104,6 +115,20 @@ 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 @@ -111,22 +136,6 @@ func restartCmd(c *cliconfig.RestartValues) error { logrus.Debugf("Setting maximum workers to %d", maxWorkers) - if rootless.IsRootless() { - // With rootless containers we cannot really restart an existing container - // as we would need to join the mount namespace as well to be able to reuse - // the storage. - if err := stopRootlessContainers(restartContainers, timeout, useTimeout, maxWorkers); err != nil { - return err - } - became, ret, err := rootless.BecomeRootInUserNS() - if err != nil { - return err - } - if became { - os.Exit(ret) - } - } - // We now have a slice of all the containers to be restarted. Iterate them to // create restart Funcs with a timeout as needed for _, ctr := range restartContainers { @@ -149,46 +158,3 @@ func restartCmd(c *cliconfig.RestartValues) error { restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, restartFuncs) return printParallelOutput(restartErrors, errCount) } - -func stopRootlessContainers(stopContainers []*libpod.Container, timeout uint, useTimeout bool, maxWorkers int) error { - var stopFuncs []shared.ParallelWorkerInput - for _, ctr := range stopContainers { - state, err := ctr.State() - if err != nil { - return err - } - if state != libpod.ContainerStateRunning { - continue - } - - ctrTimeout := ctr.StopTimeout() - if useTimeout { - ctrTimeout = timeout - } - - c := ctr - f := func() error { - return c.StopWithTimeout(ctrTimeout) - } - - stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ - ContainerID: c.ID(), - ParallelFunc: f, - }) - - restartErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - var lastError error - for _, result := range restartErrors { - if result != nil { - if errCount > 1 { - fmt.Println(result.Error()) - } - lastError = result - } - } - if lastError != nil { - return lastError - } - } - return nil -} diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go index 73d355734..0f6828432 100644 --- a/cmd/podman/restore.go +++ b/cmd/podman/restore.go @@ -40,6 +40,7 @@ var ( func init() { restoreCommand.Command = _restoreCommand + restoreCommand.SetHelpTemplate(HelpTemplate()) restoreCommand.SetUsageTemplate(UsageTemplate()) flags := restoreCommand.Flags() flags.BoolVarP(&restoreCommand.All, "all", "a", false, "Restore all checkpointed containers") diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 61b049840..253771e14 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -2,12 +2,16 @@ 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" @@ -15,11 +19,9 @@ import ( var ( rmCommand cliconfig.RmValues - rmDescription = fmt.Sprintf(` -Podman rm will remove one or more containers from the host. -The container name or ID can be used. This does not remove images. -Running containers will not be removed without the -f option. -`) + rmDescription = fmt.Sprintf(`Removes one or more containers from the host. The container name or ID can be used. + + Command does not remove images. Running containers will not be removed without the -f option.`) _rmCommand = &cobra.Command{ Use: "rm [flags] CONTAINER [CONTAINER...]", Short: "Remove one or more containers", @@ -40,6 +42,7 @@ Running containers will not be removed without the -f option. func init() { rmCommand.Command = _rmCommand + rmCommand.SetHelpTemplate(HelpTemplate()) rmCommand.SetUsageTemplate(UsageTemplate()) flags := rmCommand.Flags() flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers") @@ -49,11 +52,39 @@ 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) @@ -62,6 +93,58 @@ 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 { @@ -80,6 +163,9 @@ func rmCmd(c *cliconfig.RmValues) error { return err } if err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + exitCode = 1 + } fmt.Println(err.Error()) } } @@ -114,5 +200,10 @@ func rmCmd(c *cliconfig.RmValues) error { exitCode = 1 } } + + if failureCnt > 0 { + exitCode = 125 + } + return err } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index 5b8bf1ea3..149cd8d82 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -13,7 +13,7 @@ import ( var ( rmiCommand cliconfig.RmiValues - rmiDescription = "Removes one or more locally stored images." + rmiDescription = "Removes one or more previously pulled or locally created images." _rmiCommand = cobra.Command{ Use: "rmi [flags] IMAGE [IMAGE...]", Short: "Removes one or more images from local storage", @@ -29,12 +29,17 @@ var ( } ) +func rmiInit(command *cliconfig.RmiValues) { + command.SetHelpTemplate(HelpTemplate()) + command.SetUsageTemplate(UsageTemplate()) + flags := command.Flags() + flags.BoolVarP(&command.All, "all", "a", false, "Remove all images") + flags.BoolVarP(&command.Force, "force", "f", false, "Force Removal of the image") +} + func init() { rmiCommand.Command = &_rmiCommand - rmiCommand.SetUsageTemplate(UsageTemplate()) - flags := rmiCommand.Flags() - flags.BoolVarP(&rmiCommand.All, "all", "a", false, "Remove all images") - flags.BoolVarP(&rmiCommand.Force, "force", "f", false, "Force Removal of the image") + rmiInit(&rmiCommand) } func rmiCmd(c *cliconfig.RmiValues) error { diff --git a/cmd/podman/run.go b/cmd/podman/run.go index f66b939d3..32e7b3510 100644 --- a/cmd/podman/run.go +++ b/cmd/podman/run.go @@ -10,6 +10,7 @@ import ( "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" opentracing "github.com/opentracing/opentracing-go" @@ -39,10 +40,11 @@ var ( func init() { runCommand.Command = _runCommand + runCommand.SetHelpTemplate(HelpTemplate()) runCommand.SetUsageTemplate(UsageTemplate()) flags := runCommand.Flags() flags.SetInterspersed(false) - flags.Bool("sig-proxy", true, "Proxy received signals to the process (default true)") + flags.Bool("sig-proxy", true, "Proxy received signals to the process") getCreateFlags(&runCommand.PodmanCommand) } @@ -65,7 +67,7 @@ func runCmd(c *cliconfig.RunValues) error { } defer runtime.Shutdown(false) - ctr, createConfig, err := createContainer(&c.PodmanCommand, runtime) + ctr, createConfig, err := shared.CreateContainer(getContext(), &c.PodmanCommand, runtime) if err != nil { return err } @@ -164,6 +166,10 @@ func runCmd(c *cliconfig.RunValues) error { exitCode = int(ecode) } + if c.IsSet("rm") { + runtime.RemoveContainer(ctx, ctr, false, true) + } + return nil } diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go index 5ea39e457..a896f1dc7 100644 --- a/cmd/podman/run_test.go +++ b/cmd/podman/run_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/inspect" cc "github.com/containers/libpod/pkg/spec" "github.com/docker/go-units" @@ -80,7 +81,7 @@ func getRuntimeSpec(c *cliconfig.PodmanCommand) (*spec.Spec, error) { createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData()) */ ctx := getContext() - createConfig, err := parseCreateOpts(ctx, c, nil, "alpine", generateAlpineImageData()) + createConfig, err := shared.ParseCreateOpts(ctx, c, nil, "alpine", generateAlpineImageData()) if err != nil { return nil, err } diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go index bc4e650f9..f79aa8b0e 100644 --- a/cmd/podman/runlabel.go +++ b/cmd/podman/runlabel.go @@ -10,9 +10,11 @@ import ( "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/utils" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -38,12 +40,14 @@ Executes a command as described by a container image label. func init() { runlabelCommand.Command = _runlabelCommand + runlabelCommand.SetHelpTemplate(HelpTemplate()) runlabelCommand.SetUsageTemplate(UsageTemplate()) flags := runlabelCommand.Flags() flags.StringVar(&runlabelCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&runlabelCommand.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys") flags.StringVar(&runlabelCommand.Creds, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry") flags.BoolVar(&runlabelCommand.Display, "display", false, "Preview the command that the label would run") + flags.BoolVar(&runlabelCommand.Replace, "replace", false, "Replace existing container with a new one from the image") flags.StringVar(&runlabelCommand.Name, "name", "", "Assign a name to the container") flags.StringVar(&runlabelCommand.Opt1, "opt1", "", "Optional parameter to pass for install") @@ -53,10 +57,12 @@ func init() { flags.MarkHidden("opt2") flags.MarkHidden("opt3") - flags.BoolVarP(&runlabelCommand.Pull, "pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") + flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents") flags.BoolVarP(&runlabelCommand.Quiet, "quiet", "q", false, "Suppress output information when installing images") flags.StringVar(&runlabelCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") - flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&runlabelCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") + + flags.MarkDeprecated("pull", "podman will pull if not found in local storage") } // installCmd gets the data from the command line and calls installImage @@ -95,7 +101,6 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error { if len(args) > 2 { extraArgs = args[2:] } - pull := c.Pull label := args[0] runlabelImage := args[1] @@ -131,7 +136,7 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error { } authfile := getAuthFile(c.Authfile) - runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, pull, c.Creds, dockerRegistryOptions, authfile, c.SignaturePolicy, stdOut) + runLabel, imageName, err := shared.GetRunlabel(label, runlabelImage, ctx, runtime, true, c.Creds, dockerRegistryOptions, authfile, c.SignaturePolicy, stdOut) if err != nil { return err } @@ -144,10 +149,33 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error { return err } if !c.Quiet { - fmt.Printf("Command: %s\n", strings.Join(cmd, " ")) + fmt.Printf("command: %s\n", strings.Join(cmd, " ")) if c.Display { return nil } } + + // If container already exists && --replace given -- Nuke it + if c.Replace { + for i, entry := range cmd { + if entry == "--name" { + name := cmd[i+1] + ctr, err := runtime.LookupContainer(name) + if err != nil { + if errors.Cause(err) != libpod.ErrNoSuchCtr { + logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error()) + return err + } + } else { + logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name) + if err := runtime.RemoveContainer(ctx, ctr, true, false); err != nil { + return err + } + } + break + } + } + } + return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...) } diff --git a/cmd/podman/save.go b/cmd/podman/save.go index 3bc283772..c10679740 100644 --- a/cmd/podman/save.go +++ b/cmd/podman/save.go @@ -5,6 +5,7 @@ import ( "strings" "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/util" "github.com/pkg/errors" @@ -23,9 +24,7 @@ var validFormats = []string{ociManifestDir, ociArchive, v2s2ManifestDir, v2s2Arc var ( saveCommand cliconfig.SaveValues - saveDescription = ` - Save an image to docker-archive or oci-archive on the local machine. - Default is docker-archive` + saveDescription = `Save an image to docker-archive or oci-archive on the local machine. Default is docker-archive.` _saveCommand = &cobra.Command{ Use: "save [flags] IMAGE", @@ -54,11 +53,12 @@ var ( func init() { saveCommand.Command = _saveCommand + saveCommand.SetHelpTemplate(HelpTemplate()) saveCommand.SetUsageTemplate(UsageTemplate()) flags := saveCommand.Flags() flags.BoolVar(&saveCommand.Compress, "compress", false, "Compress tarball image layers when saving to a directory using the 'dir' transport. (default is same compression type as source)") flags.StringVar(&saveCommand.Format, "format", v2s2Archive, "Save image to oci-archive, oci-dir (directory with oci manifest type), docker-archive, docker-dir (directory with v2s2 manifest type)") - flags.StringVarP(&saveCommand.Output, "output", "o", "/dev/stdout", "Write to a file, default is STDOUT") + flags.StringVarP(&saveCommand.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)") flags.BoolVarP(&saveCommand.Quiet, "quiet", "q", false, "Suppress the output") } @@ -79,14 +79,14 @@ func saveCmd(c *cliconfig.SaveValues) error { return errors.Errorf("--compress can only be set when --format is either 'oci-dir' or 'docker-dir'") } - output := c.Output - if output == "/dev/stdout" { + if len(c.Output) == 0 { fi := os.Stdout if logrus.IsTerminal(fi) { return errors.Errorf("refusing to save to terminal. Use -o flag or redirect") } + c.Output = "/dev/stdout" } - if err := validateFileName(output); err != nil { + if err := parse.ValidateFileName(c.Output); err != nil { return err } return runtime.SaveImage(getContext(), c) diff --git a/cmd/podman/search.go b/cmd/podman/search.go index 5c14f1ff1..a10b9d419 100644 --- a/cmd/podman/search.go +++ b/cmd/podman/search.go @@ -1,11 +1,12 @@ package main import ( + "reflect" "strings" + "github.com/containers/buildah/pkg/formats" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -18,9 +19,9 @@ const ( var ( searchCommand cliconfig.SearchValues - searchDescription = ` - Search registries for a given image. Can search all the default registries or a specific registry. - Can limit the number of results, and filter the output based on certain conditions.` + searchDescription = `Search registries for a given image. Can search all the default registries or a specific registry. + + Users can limit the number of results, and filter the output based on certain conditions.` _searchCommand = &cobra.Command{ Use: "search [flags] TERM", Short: "Search registry for image", @@ -38,6 +39,7 @@ var ( func init() { searchCommand.Command = _searchCommand + searchCommand.SetHelpTemplate(HelpTemplate()) searchCommand.SetUsageTemplate(UsageTemplate()) flags := searchCommand.Flags() flags.StringVar(&searchCommand.Authfile, "authfile", "", "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override") @@ -45,7 +47,7 @@ func init() { flags.StringVar(&searchCommand.Format, "format", "", "Change the output format to a Go template") flags.IntVar(&searchCommand.Limit, "limit", 0, "Limit the number of results") flags.BoolVar(&searchCommand.NoTrunc, "no-trunc", false, "Do not truncate the output") - flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") + flags.BoolVar(&searchCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries") } func searchCmd(c *cliconfig.SearchValues) error { @@ -78,7 +80,10 @@ func searchCmd(c *cliconfig.SearchValues) error { return err } format := genSearchFormat(c.Format) - out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: results[0].HeaderMap()} + if len(results) == 0 { + return nil + } + out := formats.StdoutTemplateArray{Output: searchToGeneric(results), Template: format, Fields: genSearchOutputMap()} formats.Writer(out).Out() return nil } @@ -98,3 +103,16 @@ func searchToGeneric(params []image.SearchResult) (genericParams []interface{}) } return genericParams } + +func genSearchOutputMap() map[string]string { + io := image.SearchResult{} + v := reflect.Indirect(reflect.ValueOf(io)) + values := make(map[string]string) + + for i := 0; i < v.NumField(); i++ { + key := v.Type().Field(i).Name + value := key + values[key] = strings.ToUpper(splitCamelCase(value)) + } + return values +} diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go index 81811e0f2..6826191c5 100644 --- a/cmd/podman/shared/container.go +++ b/cmd/podman/shared/container.go @@ -3,11 +3,11 @@ package shared import ( "context" "fmt" - "github.com/google/shlex" "io" "os" "path/filepath" "regexp" + "sort" "strconv" "strings" "sync" @@ -21,6 +21,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" + "github.com/google/shlex" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -523,6 +524,7 @@ func GetCtrInspectInfo(config *libpod.ContainerConfig, ctrInspectData *inspect.C StopSignal: config.StopSignal, Cmd: config.Spec.Process.Args, Entrypoint: strings.Join(createArtifact.Entrypoint, " "), + Healthcheck: config.HealthCheckConfig, }, } return data, nil @@ -582,18 +584,93 @@ func getCgroup(spec *specs.Spec) string { return cgroup } +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} + // portsToString converts the ports used to a string of the from "port1, port2" +// also groups continuous list of ports in readable format. func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } var portDisplay []string if len(ports) == 0 { return "" } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) + }) + + // portGroupMap is used for grouping continuous ports + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + for _, v := range ports { + hostIP := v.HostIP if hostIP == "" { hostIP = "0.0.0.0" } - portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + // if hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // this list is required to travese portGroupMap + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // for each portMapKey, format group list and appned to output string + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) } return strings.Join(portDisplay, ", ") } @@ -665,6 +742,14 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s return envmap["OPT2"] case "OPT3": return envmap["OPT3"] + case "PWD": + // I would prefer to use os.getenv but it appears PWD is not in the os env list + d, err := os.Getwd() + if err != nil { + logrus.Error("unable to determine current working directory") + return "" + } + return d } return "" } diff --git a/cmd/podman/shared/create.go b/cmd/podman/shared/create.go new file mode 100644 index 000000000..5ce0b8865 --- /dev/null +++ b/cmd/podman/shared/create.go @@ -0,0 +1,905 @@ +package shared + +import ( + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + "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" + ann "github.com/containers/libpod/pkg/annotations" + "github.com/containers/libpod/pkg/inspect" + ns "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/docker/go-connections/nat" + "github.com/docker/go-units" + "github.com/google/shlex" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" + "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) { + var ( + healthCheck *manifest.Schema2HealthConfig + ) + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(ctx, "createContainer") + defer span.Finish() + } + + rtc := runtime.GetConfig() + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.InputArgs[0] + } + + var err error + var cidFile *os.File + if c.IsSet("cidfile") && os.Geteuid() == 0 { + cidFile, err = libpod.OpenExclusiveFile(c.String("cidfile")) + if err != nil && os.IsExist(err) { + return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile")) + } + if err != nil { + return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile")) + } + defer cidFile.Close() + defer cidFile.Sync() + } + + imageName := "" + var data *inspect.ImageData = nil + + if rootfs == "" && !rootless.SkipStorageSetup() { + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stderr + } + + newImage, err := runtime.ImageRuntime().New(ctx, c.InputArgs[0], rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, false, nil) + if err != nil { + return nil, nil, err + } + data, err = newImage.Inspect(ctx) + names := newImage.Names() + if len(names) > 0 { + imageName = names[0] + } else { + imageName = newImage.ID() + } + + var healthCheckCommandInput string + // if the user disabled the healthcheck with "none", we skip adding it + healthCheckCommandInput = c.String("healthcheck-command") + + // the user didnt disable the healthcheck but did pass in a healthcheck command + // now we need to make a healthcheck from the commandline input + if healthCheckCommandInput != "none" { + if len(healthCheckCommandInput) > 0 { + healthCheck, err = makeHealthCheckFromCli(c) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to create healthcheck") + } + } else { + // the user did not disable the health check and did not pass in a healthcheck + // command as input. so now we add healthcheck if it exists AND is correct mediatype + _, mediaType, err := newImage.Manifest(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID()) + } + if mediaType == manifest.DockerV2Schema2MediaType { + healthCheck, err = newImage.GetHealthCheck(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0]) + } + } + } + } + } + createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, data) + if err != nil { + return nil, nil, err + } + + // Because parseCreateOpts does derive anything from the image, we add health check + // at this point. The rest is done by WithOptions. + createConfig.HealthCheck = healthCheck + + ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, nil) + if err != nil { + return nil, nil, err + } + if cidFile != nil { + _, err = cidFile.WriteString(ctr.ID()) + if err != nil { + logrus.Error(err) + } + + } + + logrus.Debugf("New container created %q", ctr.ID()) + return ctr, createConfig, nil +} + +func parseSecurityOpt(config *cc.CreateConfig, securityOpts []string) error { + var ( + labelOpts []string + ) + + if config.PidMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if config.PidMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + if config.IpcMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if config.IpcMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + for _, opt := range securityOpts { + if opt == "no-new-privileges" { + config.NoNewPrivs = true + } else { + con := strings.SplitN(opt, "=", 2) + if len(con) != 2 { + return fmt.Errorf("Invalid --security-opt 1: %q", opt) + } + + switch con[0] { + case "label": + labelOpts = append(labelOpts, con[1]) + case "apparmor": + config.ApparmorProfile = con[1] + case "seccomp": + config.SeccompProfilePath = con[1] + default: + return fmt.Errorf("Invalid --security-opt 2: %q", opt) + } + } + } + + if config.SeccompProfilePath == "" { + if _, err := os.Stat(libpod.SeccompOverridePath); err == nil { + config.SeccompProfilePath = libpod.SeccompOverridePath + } else { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompOverridePath) + } + if _, err := os.Stat(libpod.SeccompDefaultPath); err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompDefaultPath) + } + } else { + config.SeccompProfilePath = libpod.SeccompDefaultPath + } + } + } + config.LabelOpts = labelOpts + return nil +} + +func configureEntrypoint(c *cliconfig.PodmanCommand, data *inspect.ImageData) []string { + entrypoint := []string{} + if c.IsSet("entrypoint") { + // Force entrypoint to "" + if c.String("entrypoint") == "" { + return entrypoint + } + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil { + return entrypoint + } + // Return entrypoint as a single command + return []string{c.String("entrypoint")} + } + if data != nil { + return data.Config.Entrypoint + } + return entrypoint +} + +func configurePod(c *cliconfig.PodmanCommand, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, error) { + pod, err := runtime.LookupPod(podName) + if err != nil { + return namespaces, err + } + podInfraID, err := pod.InfraContainerID() + if err != nil { + return namespaces, err + } + if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { + namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { + namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { + namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) { + namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) { + namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID) + } + return namespaces, nil +} + +// 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) { + var ( + inputCommand, command []string + memoryLimit, memoryReservation, memorySwap, memoryKernel int64 + blkioWeight uint16 + namespaces map[string]string + ) + if c.IsSet("restart") { + return nil, errors.Errorf("--restart option is not supported.\nUse systemd unit files for restarting containers") + } + + idmappings, err := util.ParseIDMapping(c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) + if err != nil { + return nil, err + } + + if c.String("mac-address") != "" { + return nil, errors.Errorf("--mac-address option not currently supported") + } + + imageID := "" + + inputCommand = c.InputArgs[1:] + if data != nil { + imageID = data.ID + } + + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.InputArgs[0] + } + + sysctl, err := validateSysctl(c.StringSlice("sysctl")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for sysctl") + } + + if c.String("memory") != "" { + memoryLimit, err = units.RAMInBytes(c.String("memory")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory") + } + } + if c.String("memory-reservation") != "" { + memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory-reservation") + } + } + if c.String("memory-swap") != "" { + memorySwap, err = units.RAMInBytes(c.String("memory-swap")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory-swap") + } + } + if c.String("kernel-memory") != "" { + memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for kernel-memory") + } + } + if c.String("blkio-weight") != "" { + u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for blkio-weight") + } + blkioWeight = uint16(u) + } + var mountList []spec.Mount + if mountList, err = parseMounts(c.StringArray("mount")); err != nil { + return nil, err + } + + if err = parseVolumes(c.StringArray("volume")); err != nil { + return nil, err + } + + if err = parseVolumesFrom(c.StringSlice("volumes-from")); err != nil { + return nil, err + } + + tty := c.Bool("tty") + + if c.Flag("cpu-period").Changed && c.Flag("cpus").Changed { + return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") + } + if c.Flag("cpu-quota").Changed && c.Flag("cpus").Changed { + return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") + } + + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.Config.ExposedPorts) + if err != nil { + return nil, err + } + } + + // Kernel Namespaces + // TODO Fix handling of namespace from pod + // Instead of integrating here, should be done in libpod + // However, that also involves setting up security opts + // when the pod's namespace is integrated + namespaceNet := c.String("network") + if c.Flag("net").Changed { + namespaceNet = c.String("net") + } + namespaces = map[string]string{ + "pid": c.String("pid"), + "net": namespaceNet, + "ipc": c.String("ipc"), + "user": c.String("userns"), + "uts": c.String("uts"), + } + + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } + if c.IsSet("pod") { + if strings.HasPrefix(originalPodName, "new:") { + if rootless.IsRootless() { + // To create a new pod, we must immediately create the userns. + became, ret, err := rootless.BecomeRootInUserNS() + if err != nil { + return nil, err + } + if became { + os.Exit(ret) + } + } + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, err = configurePod(c, runtime, namespaces, podName) + if err != nil { + return nil, err + } + } + + pidMode := ns.PidMode(namespaces["pid"]) + if !cc.Valid(string(pidMode), pidMode) { + return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) + } + + usernsMode := ns.UsernsMode(namespaces["user"]) + if !cc.Valid(string(usernsMode), usernsMode) { + return nil, errors.Errorf("--userns %q is not valid", namespaces["user"]) + } + + utsMode := ns.UTSMode(namespaces["uts"]) + if !cc.Valid(string(utsMode), utsMode) { + return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"]) + } + + ipcMode := ns.IpcMode(namespaces["ipc"]) + if !cc.Valid(string(ipcMode), ipcMode) { + return nil, errors.Errorf("--ipc %q is not valid", ipcMode) + } + + // Make sure if network is set to container namespace, port binding is not also being asked for + netMode := ns.NetworkMode(namespaces["net"]) + if netMode.IsContainer() { + if len(portBindings) > 0 { + return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") + } + } + + // USER + user := c.String("user") + if user == "" { + if data == nil { + user = "0" + } else { + user = data.Config.User + } + } + + // STOP SIGNAL + stopSignal := syscall.SIGTERM + signalString := "" + if data != nil { + signalString = data.Config.StopSignal + } + if c.IsSet("stop-signal") { + signalString = c.String("stop-signal") + } + if signalString != "" { + stopSignal, err = signal.ParseSignal(signalString) + if err != nil { + return nil, err + } + } + + // ENVIRONMENT VARIABLES + env := defaultEnvVariables + if data != nil { + for _, e := range data.Config.Env { + split := strings.SplitN(e, "=", 2) + if len(split) > 1 { + env[split[0]] = split[1] + } else { + env[split[0]] = "" + } + } + } + if err := parse.ReadKVStrings(env, c.StringSlice("env-file"), c.StringArray("env")); err != nil { + return nil, errors.Wrapf(err, "unable to process environment variables") + } + + // LABEL VARIABLES + labels, err := GetAllLabels(c.StringSlice("label-file"), c.StringArray("label")) + if err != nil { + return nil, errors.Wrapf(err, "unable to process labels") + } + if data != nil { + for key, val := range data.Config.Labels { + if _, ok := labels[key]; !ok { + labels[key] = val + } + } + } + + // ANNOTATIONS + annotations := make(map[string]string) + // First, add our default annotations + annotations[ann.ContainerType] = "sandbox" + annotations[ann.TTY] = "false" + if tty { + annotations[ann.TTY] = "true" + } + if data != nil { + // Next, add annotations from the image + for key, value := range data.Annotations { + annotations[key] = value + } + } + // Last, add user annotations + for _, annotation := range c.StringSlice("annotation") { + splitAnnotation := strings.SplitN(annotation, "=", 2) + if len(splitAnnotation) < 2 { + return nil, errors.Errorf("Annotations must be formatted KEY=VALUE") + } + annotations[splitAnnotation[0]] = splitAnnotation[1] + } + + // WORKING DIRECTORY + workDir := "/" + if c.IsSet("workdir") || c.IsSet("w") { + workDir = c.String("workdir") + } else if data != nil && data.Config.WorkingDir != "" { + workDir = data.Config.WorkingDir + } + + entrypoint := configureEntrypoint(c, data) + // Build the command + // If we have an entry point, it goes first + if len(entrypoint) > 0 { + command = entrypoint + } + if len(inputCommand) > 0 { + // User command overrides data CMD + command = append(command, inputCommand...) + } else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") { + // If not user command, add CMD + command = append(command, data.Config.Cmd...) + } + + if data != nil && len(command) == 0 { + return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") + } + + // SHM Size + shmSize, err := units.FromHumanSize(c.String("shm-size")) + if err != nil { + return nil, errors.Wrapf(err, "unable to translate --shm-size") + } + + // Verify the additional hosts are in correct format + for _, host := range c.StringSlice("add-host") { + if _, err := parse.ValidateExtraHost(host); err != nil { + return nil, err + } + } + + // Check for . and dns-search domains + if util.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 { + return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + } + + // Validate domains are good + for _, dom := range c.StringSlice("dns-search") { + if _, err := parse.ValidateDomain(dom); err != nil { + return nil, err + } + } + + var ImageVolumes map[string]struct{} + if data != nil && c.String("image-volume") != "ignore" { + ImageVolumes = data.Config.Volumes + } + + var imageVolType = map[string]string{ + "bind": "", + "tmpfs": "", + "ignore": "", + } + if _, ok := imageVolType[c.String("image-volume")]; !ok { + return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) + } + + var systemd bool + if command != nil && c.Bool("systemd") && ((filepath.Base(command[0]) == "init") || (filepath.Base(command[0]) == "systemd")) { + systemd = true + if signalString == "" { + stopSignal, err = signal.ParseSignal("RTMIN+3") + if err != nil { + return nil, errors.Wrapf(err, "error parsing systemd signal") + } + } + } + // 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 { + network = c.String("net") + } + + var memorySwappiness int64 + if c.Flags().Lookup("memory-swappiness") != nil { + memorySwappiness, _ = c.Flags().GetInt64("memory-swappiness") + } + config := &cc.CreateConfig{ + Runtime: runtime, + Annotations: annotations, + BuiltinImgVolumes: ImageVolumes, + ConmonPidFile: c.String("conmon-pidfile"), + ImageVolumeType: c.String("image-volume"), + CapAdd: c.StringSlice("cap-add"), + CapDrop: c.StringSlice("cap-drop"), + CgroupParent: c.String("cgroup-parent"), + Command: command, + Detach: c.Bool("detach"), + Devices: c.StringSlice("device"), + DNSOpt: c.StringSlice("dns-opt"), + DNSSearch: c.StringSlice("dns-search"), + DNSServers: c.StringSlice("dns"), + Entrypoint: entrypoint, + Env: env, + //ExposedPorts: ports, + GroupAdd: c.StringSlice("group-add"), + Hostname: c.String("hostname"), + HostAdd: c.StringSlice("add-host"), + IDMappings: idmappings, + Image: imageName, + ImageID: imageID, + Interactive: c.Bool("interactive"), + //IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6 + IPAddress: c.String("ip"), + Labels: labels, + //LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet + LogDriver: c.String("log-driver"), + LogDriverOpt: c.StringSlice("log-opt"), + MacAddress: c.String("mac-address"), + Name: c.String("name"), + Network: network, + //NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman? + IpcMode: ipcMode, + NetMode: netMode, + UtsMode: utsMode, + PidMode: pidMode, + Pod: podName, + Privileged: c.Bool("privileged"), + Publish: c.StringSlice("publish"), + PublishAll: c.Bool("publish-all"), + PortBindings: portBindings, + Quiet: c.Bool("quiet"), + ReadOnlyRootfs: c.Bool("read-only"), + Resources: cc.CreateResourceConfig{ + BlkioWeight: blkioWeight, + BlkioWeightDevice: c.StringSlice("blkio-weight-device"), + CPUShares: c.Uint64("cpu-shares"), + CPUPeriod: c.Uint64("cpu-period"), + CPUsetCPUs: c.String("cpuset-cpus"), + CPUsetMems: c.String("cpuset-mems"), + CPUQuota: c.Int64("cpu-quota"), + CPURtPeriod: c.Uint64("cpu-rt-period"), + CPURtRuntime: c.Int64("cpu-rt-runtime"), + CPUs: c.Float64("cpus"), + DeviceReadBps: c.StringSlice("device-read-bps"), + DeviceReadIOps: c.StringSlice("device-read-iops"), + DeviceWriteBps: c.StringSlice("device-write-bps"), + DeviceWriteIOps: c.StringSlice("device-write-iops"), + DisableOomKiller: c.Bool("oom-kill-disable"), + ShmSize: shmSize, + Memory: memoryLimit, + MemoryReservation: memoryReservation, + MemorySwap: memorySwap, + MemorySwappiness: int(memorySwappiness), + KernelMemory: memoryKernel, + OomScoreAdj: c.Int("oom-score-adj"), + PidsLimit: c.Int64("pids-limit"), + Ulimit: c.StringSlice("ulimit"), + }, + Rm: c.Bool("rm"), + StopSignal: stopSignal, + StopTimeout: c.Uint("stop-timeout"), + Sysctl: sysctl, + Systemd: systemd, + Tmpfs: c.StringSlice("tmpfs"), + Tty: tty, + User: user, + UsernsMode: usernsMode, + Mounts: mountList, + Volumes: c.StringArray("volume"), + WorkDir: workDir, + Rootfs: rootfs, + VolumesFrom: c.StringSlice("volumes-from"), + Syslog: c.GlobalFlags.Syslog, + } + if c.Bool("init") { + initPath := c.String("init-path") + if initPath == "" { + initPath = runtime.GetConfig().InitPath + } + if err := config.AddContainerInitBinary(initPath); err != nil { + return nil, err + } + } + + if config.Privileged { + config.LabelOpts = label.DisableSecOpt() + } else { + if err := parseSecurityOpt(config, c.StringArray("security-opt")); err != nil { + return nil, err + } + } + config.SecurityOpts = c.StringArray("security-opt") + warnings, err := verifyContainerResources(config, false) + if err != nil { + return nil, err + } + for _, warning := range warnings { + fmt.Fprintln(os.Stderr, warning) + } + return config, nil +} + +type namespace interface { + IsContainer() bool + 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 { + return nil, err + } + + options, err := createConfig.GetContainerCreateOptions(r, pod) + 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 { + return nil, err + } + + createConfigJSON, err := json.Marshal(createConfig) + if err != nil { + return nil, err + } + if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { + return nil, err + } + return ctr, nil +} + +var defaultEnvVariables = map[string]string{ + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM": "xterm", +} + +func makeHealthCheckFromCli(c *cliconfig.PodmanCommand) (*manifest.Schema2HealthConfig, error) { + inCommand := c.String("healthcheck-command") + inInterval := c.String("healthcheck-interval") + inRetries := c.Uint("healthcheck-retries") + inTimeout := c.String("healthcheck-timeout") + inStartPeriod := c.String("healthcheck-start-period") + + // Every healthcheck requires a command + if len(inCommand) == 0 { + return nil, errors.New("Must define a healthcheck command for all healthchecks") + } + + cmd, err := shlex.Split(inCommand) + if err != nil { + return nil, errors.Wrap(err, "failed to parse healthcheck command") + } + hc := manifest.Schema2HealthConfig{ + Test: cmd, + } + + if inInterval == "disable" { + inInterval = "0" + } + intervalDuration, err := time.ParseDuration(inInterval) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", inInterval) + } + + hc.Interval = intervalDuration + + if inRetries < 1 { + return nil, errors.New("healthcheck-retries must be greater than 0.") + } + hc.Retries = int(inRetries) + timeoutDuration, err := time.ParseDuration(inTimeout) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", inTimeout) + } + if timeoutDuration < time.Duration(time.Second*1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration, err := time.ParseDuration(inStartPeriod) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod) + } + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be a 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + + return &hc, nil +} diff --git a/cmd/podman/create_cli.go b/cmd/podman/shared/create_cli.go index ae0549687..4f9cb1699 100644 --- a/cmd/podman/create_cli.go +++ b/cmd/podman/shared/create_cli.go @@ -1,4 +1,4 @@ -package main +package shared import ( "fmt" @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/cmd/podman/shared/parse" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/sysinfo" "github.com/docker/go-units" @@ -19,9 +20,10 @@ const ( linuxMinMemory = 4194304 ) -func getAllLabels(labelFile, inputLabels []string) (map[string]string, error) { +// GetAllLabels ... +func GetAllLabels(labelFile, inputLabels []string) (map[string]string, error) { labels := make(map[string]string) - labelErr := readKVStrings(labels, labelFile, inputLabels) + labelErr := parse.ReadKVStrings(labels, labelFile, inputLabels) if labelErr != nil { return labels, errors.Wrapf(labelErr, "unable to process labels from --label and label-file") } @@ -149,12 +151,12 @@ func parseMounts(mounts []string) ([]spec.Mount, error) { if mountInfo.Type == "tmpfs" { return nil, errors.Errorf("cannot use src= on a tmpfs file system") } - if err := validateVolumeHostDir(kv[1]); err != nil { + if err := ValidateVolumeHostDir(kv[1]); err != nil { return nil, err } mountInfo.Source = kv[1] case "target", "dst", "destination": - if err := validateVolumeCtrDir(kv[1]); err != nil { + if err := ValidateVolumeCtrDir(kv[1]); err != nil { return nil, err } mountInfo.Destination = kv[1] @@ -173,10 +175,10 @@ func parseVolumes(volumes []string) error { if len(arr) < 2 { return errors.Errorf("incorrect volume format %q, should be host-dir:ctr-dir[:option]", volume) } - if err := validateVolumeHostDir(arr[0]); err != nil { + if err := ValidateVolumeHostDir(arr[0]); err != nil { return err } - if err := validateVolumeCtrDir(arr[1]); err != nil { + if err := ValidateVolumeCtrDir(arr[1]); err != nil { return err } if len(arr) > 2 { @@ -203,7 +205,8 @@ func parseVolumesFrom(volumesFrom []string) error { return nil } -func validateVolumeHostDir(hostDir string) error { +// ValidateVolumeHostDir ... +func ValidateVolumeHostDir(hostDir string) error { if len(hostDir) == 0 { return errors.Errorf("host directory cannot be empty") } @@ -217,7 +220,8 @@ func validateVolumeHostDir(hostDir string) error { return nil } -func validateVolumeCtrDir(ctrDir string) error { +// ValidateVolumeCtrDir ... +func ValidateVolumeCtrDir(ctrDir string) error { if len(ctrDir) == 0 { return errors.Errorf("container directory cannot be empty") } diff --git a/cmd/podman/create_cli_test.go b/cmd/podman/shared/create_cli_test.go index 9db007ff3..fea1a2390 100644 --- a/cmd/podman/create_cli_test.go +++ b/cmd/podman/shared/create_cli_test.go @@ -1,4 +1,4 @@ -package main +package shared import ( "io/ioutil" @@ -42,20 +42,20 @@ func TestValidateSysctlBadSysctl(t *testing.T) { func TestGetAllLabels(t *testing.T) { fileLabels := []string{} - labels, _ := getAllLabels(fileLabels, Var1) + labels, _ := GetAllLabels(fileLabels, Var1) assert.Equal(t, len(labels), 2) } func TestGetAllLabelsBadKeyValue(t *testing.T) { inLabels := []string{"=badValue", "="} fileLabels := []string{} - _, err := getAllLabels(fileLabels, inLabels) + _, err := GetAllLabels(fileLabels, inLabels) assert.Error(t, err, assert.AnError) } func TestGetAllLabelsBadLabelFile(t *testing.T) { fileLabels := []string{"/foobar5001/be"} - _, err := getAllLabels(fileLabels, Var1) + _, err := GetAllLabels(fileLabels, Var1) assert.Error(t, err, assert.AnError) } @@ -65,6 +65,6 @@ func TestGetAllLabelsFile(t *testing.T) { defer os.Remove(tFile) assert.NoError(t, err) fileLabels := []string{tFile} - result, _ := getAllLabels(fileLabels, Var1) + result, _ := GetAllLabels(fileLabels, Var1) assert.Equal(t, len(result), 3) } diff --git a/cmd/podman/shared/events.go b/cmd/podman/shared/events.go new file mode 100644 index 000000000..c62044271 --- /dev/null +++ b/cmd/podman/shared/events.go @@ -0,0 +1,115 @@ +package shared + +import ( + "fmt" + "strings" + "time" + + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +func generateEventFilter(filter, filterValue string) (func(e *events.Event) bool, error) { + switch strings.ToUpper(filter) { + case "CONTAINER": + return func(e *events.Event) bool { + if e.Type != events.Container { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "EVENT", "STATUS": + return func(e *events.Event) bool { + return fmt.Sprintf("%s", e.Status) == filterValue + }, nil + case "IMAGE": + return func(e *events.Event) bool { + if e.Type != events.Image { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "POD": + return func(e *events.Event) bool { + if e.Type != events.Pod { + return false + } + if e.Name == filterValue { + return true + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "VOLUME": + return func(e *events.Event) bool { + if e.Type != events.Volume { + return false + } + return strings.HasPrefix(e.ID, filterValue) + }, nil + case "TYPE": + return func(e *events.Event) bool { + return fmt.Sprintf("%s", e.Type) == filterValue + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + +func generateEventSinceOption(timeSince time.Time) func(e *events.Event) bool { + return func(e *events.Event) bool { + return e.Time.After(timeSince) + } +} + +func generateEventUntilOption(timeUntil time.Time) func(e *events.Event) bool { + return func(e *events.Event) bool { + return e.Time.Before(timeUntil) + + } +} + +func parseFilter(filter string) (string, string, error) { + filterSplit := strings.Split(filter, "=") + if len(filterSplit) != 2 { + return "", "", errors.Errorf("%s is an invalid filter", filter) + } + return filterSplit[0], filterSplit[1], nil +} + +func GenerateEventOptions(filters []string, since, until string) ([]events.EventFilter, error) { + var options []events.EventFilter + for _, filter := range filters { + key, val, err := parseFilter(filter) + if err != nil { + return nil, err + } + funcFilter, err := generateEventFilter(key, val) + if err != nil { + return nil, err + } + options = append(options, funcFilter) + } + + if len(since) > 0 { + timeSince, err := util.ParseInputTime(since) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert since time of %s", since) + } + options = append(options, generateEventSinceOption(timeSince)) + } + + if len(until) > 0 { + timeUntil, err := util.ParseInputTime(until) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert until time of %s", until) + } + options = append(options, generateEventUntilOption(timeUntil)) + } + return options, nil +} diff --git a/cmd/podman/parse.go b/cmd/podman/shared/parse/parse.go index 2e4959656..a3751835b 100644 --- a/cmd/podman/parse.go +++ b/cmd/podman/shared/parse/parse.go @@ -1,7 +1,7 @@ //nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o -package main +package parse import ( "bufio" @@ -50,7 +50,7 @@ var ( // validateExtraHost validates that the specified string is a valid extrahost and returns it. // ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6). // for add-host flag -func validateExtraHost(val string) (string, error) { //nolint +func ValidateExtraHost(val string) (string, error) { //nolint // allow for IPv6 addresses in extra hosts by only splitting on first ":" arr := strings.SplitN(val, ":", 2) if len(arr) != 2 || len(arr[0]) == 0 { @@ -140,10 +140,10 @@ func validateDNSSearch(val string) (string, error) { //nolint if val = strings.Trim(val, " "); val == "." { return val, nil } - return validateDomain(val) + return ValidateDomain(val) } -func validateDomain(val string) (string, error) { +func ValidateDomain(val string) (string, error) { if alphaRegexp.FindString(val) == "" { return "", fmt.Errorf("%s is not a valid domain", val) } @@ -181,7 +181,7 @@ func doesEnvExist(name string) bool { // reads a file of line terminated key=value pairs, and overrides any keys // present in the file with additional pairs specified in the override parameter // for env-file and labels-file flags -func readKVStrings(env map[string]string, files []string, override []string) error { +func ReadKVStrings(env map[string]string, files []string, override []string) error { for _, ef := range files { if err := parseEnvFile(env, ef); err != nil { return err @@ -494,9 +494,9 @@ func stringSlicetoUint32Slice(inputSlice []string) ([]uint32, error) { return outputSlice, nil } -// validateFileName returns an error if filename contains ":" +// ValidateFileName returns an error if filename contains ":" // as it is currently not supported -func validateFileName(filename string) error { +func ValidateFileName(filename string) error { if strings.Contains(filename, ":") { return errors.Errorf("invalid filename (should not contain ':') %q", filename) } diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go index 5f65c40ac..4d936d61c 100644 --- a/cmd/podman/shared/pod.go +++ b/cmd/podman/shared/pod.go @@ -136,3 +136,5 @@ func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { } return portBindings, nil } + +var DefaultKernelNamespaces = "cgroup,ipc,net,uts" diff --git a/cmd/podman/sign.go b/cmd/podman/sign.go index 2cf228d01..06418e4a5 100644 --- a/cmd/podman/sign.go +++ b/cmd/podman/sign.go @@ -22,7 +22,7 @@ import ( var ( signCommand cliconfig.SignValues - signDescription = "Create a signature file that can be used later to verify the image" + signDescription = "Create a signature file that can be used later to verify the image." _signCommand = &cobra.Command{ Use: "sign [flags] IMAGE [IMAGE...]", Short: "Sign an image", @@ -39,6 +39,7 @@ var ( func init() { signCommand.Command = _signCommand + signCommand.SetHelpTemplate(HelpTemplate()) signCommand.SetUsageTemplate(UsageTemplate()) flags := signCommand.Flags() flags.StringVarP(&signCommand.Directory, "directory", "d", "", "Define an alternate directory to store signatures") diff --git a/cmd/podman/start.go b/cmd/podman/start.go index 3ce04ea79..cf406cf66 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -15,11 +15,8 @@ import ( var ( startCommand cliconfig.StartValues - startDescription = ` - podman start + startDescription = `Starts one or more containers. The container name or ID can be used.` - Starts one or more containers. The container name or ID can be used. -` _startCommand = &cobra.Command{ Use: "start [flags] CONTAINER [CONTAINER...]", Short: "Start one or more containers", @@ -37,13 +34,14 @@ var ( func init() { startCommand.Command = _startCommand + startCommand.SetHelpTemplate(HelpTemplate()) startCommand.SetUsageTemplate(UsageTemplate()) flags := startCommand.Flags() flags.BoolVarP(&startCommand.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR") flags.StringVar(&startCommand.DetachKeys, "detach-keys", "", "Override the key sequence for detaching a container. Format is a single character [a-Z] or ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _") flags.BoolVarP(&startCommand.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached") flags.BoolVarP(&startCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - flags.BoolVar(&startCommand.SigProxy, "sig-proxy", true, "Proxy received signals to the process (default true if attaching, false otherwise)") + flags.BoolVar(&startCommand.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)") markFlagHiddenForRemoteClient("latest", flags) } @@ -64,14 +62,10 @@ func startCmd(c *cliconfig.StartValues) error { return errors.Errorf("you cannot start and attach multiple containers at once") } - sigProxy := c.SigProxy + sigProxy := c.SigProxy || attach if sigProxy && !attach { - if c.Flag("sig-proxy").Changed { - return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") - } else { - sigProxy = false - } + return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go index dcb274471..d379dbad7 100644 --- a/cmd/podman/stats.go +++ b/cmd/podman/stats.go @@ -8,8 +8,8 @@ import ( "time" tm "github.com/buger/goterm" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/docker/go-units" @@ -31,10 +31,10 @@ type statsOutputParams struct { var ( statsCommand cliconfig.StatsValues - statsDescription = "display a live stream of one or more containers' resource usage statistics" + statsDescription = "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers." _statsCommand = &cobra.Command{ Use: "stats [flags] CONTAINER [CONTAINER...]", - Short: "Display percentage of CPU, memory, network I/O, block I/O and PIDs for one or more containers", + Short: "Display a live stream of container resource usage statistics", Long: statsDescription, RunE: func(cmd *cobra.Command, args []string) error { statsCommand.InputArgs = args @@ -52,6 +52,7 @@ var ( func init() { statsCommand.Command = _statsCommand + statsCommand.SetHelpTemplate(HelpTemplate()) statsCommand.SetUsageTemplate(UsageTemplate()) flags := statsCommand.Flags() flags.BoolVarP(&statsCommand.All, "all", "a", false, "Show all containers. Only running containers are shown by default. The default is false") diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index ab9a2cf38..2a1470ad0 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -2,27 +2,22 @@ package main import ( "fmt" + "reflect" "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/adapter" "github.com/containers/libpod/pkg/rootless" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( stopCommand cliconfig.StopValues - stopDescription = ` - podman stop + stopDescription = `Stops one or more running containers. The container name or ID can be used. - Stops one or more running containers. The container name or ID can be used. - A timeout to forcibly stop the container can also be set but defaults to 10 - seconds otherwise. -` + A timeout to forcibly stop the container can also be set but defaults to 10 seconds otherwise.` _stopCommand = &cobra.Command{ Use: "stop [flags] CONTAINER [CONTAINER...]", Short: "Stop one or more containers", @@ -43,6 +38,7 @@ var ( func init() { stopCommand.Command = _stopCommand + stopCommand.SetHelpTemplate(HelpTemplate()) stopCommand.SetUsageTemplate(UsageTemplate()) flags := stopCommand.Flags() flags.BoolVarP(&stopCommand.All, "all", "a", false, "Stop all running containers") @@ -52,63 +48,43 @@ func init() { markFlagHiddenForRemoteClient("latest", flags) } +// stopCmd stops a container or containers func stopCmd(c *cliconfig.StopValues) error { + if c.Flag("timeout").Changed && c.Flag("time").Changed { + return errors.New("the --timeout and --time flags are mutually exclusive") + } + if c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "stopCmd") defer span.Finish() } rootless.SetSkipStorageSetup(true) - 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) - containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") + ok, failures, err := runtime.StopContainers(getContext(), c) if err != nil { - if len(containers) == 0 { - return err - } - fmt.Println(err.Error()) + return err } - if c.Flag("timeout").Changed && c.Flag("time").Changed { - return errors.New("the --timeout and --time flags are mutually exclusive") + for _, id := range ok { + fmt.Println(id) } - var stopFuncs []shared.ParallelWorkerInput - for _, ctr := range containers { - con := ctr - var stopTimeout uint - if c.Flag("timeout").Changed || c.Flag("time").Changed { - stopTimeout = c.Timeout - } else { - stopTimeout = ctr.StopTimeout() - logrus.Debugf("Set timeout to container %s default (%d)", ctr.ID(), stopTimeout) - } - f := func() error { - if err := con.StopWithTimeout(stopTimeout); err != nil { - if errors.Cause(err) == libpod.ErrCtrStopped { - logrus.Debugf("Container %s already stopped", con.ID()) - return nil - } - return err - } - return nil - } - stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) - } + if len(failures) > 0 { + keys := reflect.ValueOf(failures).MapKeys() + lastKey := keys[len(keys)-1].String() + lastErr := failures[lastKey] + delete(failures, lastKey) - maxWorkers := shared.Parallelize("stop") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks + for _, err := range failures { + outputError(err) + } + return lastErr } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - return printParallelOutput(stopErrors, errCount) + return nil } diff --git a/cmd/podman/system.go b/cmd/podman/system.go index 741b79da5..528a594de 100644 --- a/cmd/podman/system.go +++ b/cmd/podman/system.go @@ -13,6 +13,7 @@ var ( Use: "system", Short: "Manage podman", Long: systemDescription, + RunE: commandRunE(), }, } ) diff --git a/cmd/podman/system_df.go b/cmd/podman/system_df.go new file mode 100644 index 000000000..183c5a7dd --- /dev/null +++ b/cmd/podman/system_df.go @@ -0,0 +1,639 @@ +package main + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + units "github.com/docker/go-units" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + dfSystemCommand cliconfig.SystemDfValues + dfSystemDescription = ` + podman system df + + Show podman disk usage + ` + _dfSystemCommand = &cobra.Command{ + Use: "df", + Short: "Show podman disk usage", + Long: dfSystemDescription, + RunE: func(cmd *cobra.Command, args []string) error { + dfSystemCommand.GlobalFlags = MainGlobalOpts + return dfSystemCmd(&dfSystemCommand) + }, + } +) + +type dfMetaData struct { + images []*image.Image + containers []*libpod.Container + activeContainers map[string]*libpod.Container + imagesUsedbyCtrMap map[string][]*libpod.Container + imagesUsedbyActiveCtr map[string][]*libpod.Container + volumes []*libpod.Volume + volumeUsedByContainerMap map[string][]*libpod.Container +} + +type systemDfDiskUsage struct { + Type string + Total int + Active int + Size string + Reclaimable string +} + +type imageVerboseDiskUsage struct { + Repository string + Tag string + ImageID string + Created string + Size string + SharedSize string + UniqueSize string + Containers int +} + +type containerVerboseDiskUsage struct { + ContainerID string + Image string + Command string + LocalVolumes int + Size string + Created string + Status string + Names string +} + +type volumeVerboseDiskUsage struct { + VolumeName string + Links int + Size string +} + +const systemDfDefaultFormat string = "table {{.Type}}\t{{.Total}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}" + +func init() { + dfSystemCommand.Command = _dfSystemCommand + dfSystemCommand.SetUsageTemplate(UsageTemplate()) + flags := dfSystemCommand.Flags() + flags.BoolVarP(&dfSystemCommand.Verbose, "verbose", "v", false, "Show detailed information on space usage") + flags.StringVar(&dfSystemCommand.Format, "format", "", "Pretty-print images using a Go template") +} + +func dfSystemCmd(c *cliconfig.SystemDfValues) error { + runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "Could not get runtime") + } + defer runtime.Shutdown(false) + + ctx := getContext() + + metaData, err := getDfMetaData(ctx, runtime) + if err != nil { + return errors.Wrapf(err, "error getting disk usage data") + } + + if c.Verbose { + err := verboseOutput(ctx, metaData) + if err != nil { + return err + } + return nil + } + + systemDfDiskUsages, err := getDiskUsage(ctx, runtime, metaData) + if err != nil { + return errors.Wrapf(err, "error getting output of system df") + } + format := systemDfDefaultFormat + if c.Format != "" { + format = strings.Replace(c.Format, `\t`, "\t", -1) + } + generateSysDfOutput(systemDfDiskUsages, format) + return nil +} + +func generateSysDfOutput(systemDfDiskUsages []systemDfDiskUsage, format string) { + var systemDfHeader = map[string]string{ + "Type": "TYPE", + "Total": "TOTAL", + "Active": "ACTIVE", + "Size": "SIZE", + "Reclaimable": "RECLAIMABLE", + } + out := formats.StdoutTemplateArray{Output: systemDfDiskUsageToGeneric(systemDfDiskUsages), Template: format, Fields: systemDfHeader} + formats.Writer(out).Out() +} + +func getDiskUsage(ctx context.Context, runtime *libpod.Runtime, metaData dfMetaData) ([]systemDfDiskUsage, error) { + imageDiskUsage, err := getImageDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap, metaData.imagesUsedbyActiveCtr) + if err != nil { + return nil, errors.Wrapf(err, "error getting disk usage of images") + } + containerDiskUsage, err := getContainerDiskUsage(metaData.containers, metaData.activeContainers) + if err != nil { + return nil, errors.Wrapf(err, "error getting disk usage of containers") + } + volumeDiskUsage, err := getVolumeDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) + if err != nil { + return nil, errors.Wrapf(err, "error getting disk usage of volumess") + } + + systemDfDiskUsages := []systemDfDiskUsage{imageDiskUsage, containerDiskUsage, volumeDiskUsage} + return systemDfDiskUsages, nil +} + +func getDfMetaData(ctx context.Context, runtime *libpod.Runtime) (dfMetaData, error) { + var metaData dfMetaData + images, err := runtime.ImageRuntime().GetImages() + if err != nil { + return metaData, errors.Wrapf(err, "unable to get images") + } + containers, err := runtime.GetAllContainers() + if err != nil { + return metaData, errors.Wrapf(err, "error getting all containers") + } + volumes, err := runtime.GetAllVolumes() + if err != nil { + return metaData, errors.Wrap(err, "error getting all volumes") + } + activeContainers, err := activeContainers(containers) + if err != nil { + return metaData, errors.Wrapf(err, "error getting active containers") + } + imagesUsedbyCtrMap, imagesUsedbyActiveCtr, err := imagesUsedbyCtr(containers, activeContainers) + if err != nil { + return metaData, errors.Wrapf(err, "error getting getting images used by containers") + } + metaData = dfMetaData{ + images: images, + containers: containers, + activeContainers: activeContainers, + imagesUsedbyCtrMap: imagesUsedbyCtrMap, + imagesUsedbyActiveCtr: imagesUsedbyActiveCtr, + volumes: volumes, + volumeUsedByContainerMap: volumeUsedByContainer(containers), + } + return metaData, nil +} + +func imageUniqueSize(ctx context.Context, images []*image.Image) (map[string]uint64, error) { + imgUniqueSizeMap := make(map[string]uint64) + for _, img := range images { + parentImg := img + for { + next, err := parentImg.GetParent() + if err != nil { + return nil, errors.Wrapf(err, "error getting parent of image %s", parentImg.ID()) + } + if next == nil { + break + } + parentImg = next + } + imgSize, err := img.Size(ctx) + if err != nil { + return nil, err + } + if img.ID() == parentImg.ID() { + imgUniqueSizeMap[img.ID()] = *imgSize + } else { + parentImgSize, err := parentImg.Size(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error getting size of parent image %s", parentImg.ID()) + } + imgUniqueSizeMap[img.ID()] = *imgSize - *parentImgSize + } + } + return imgUniqueSizeMap, nil +} + +func getImageDiskUsage(ctx context.Context, images []*image.Image, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { + var ( + numberOfImages int + sumSize uint64 + numberOfActiveImages int + unreclaimableSize uint64 + imageDiskUsage systemDfDiskUsage + reclaimableStr string + ) + + imgUniqueSizeMap, err := imageUniqueSize(ctx, images) + if err != nil { + return imageDiskUsage, errors.Wrapf(err, "error getting unique size of images") + } + + for _, img := range images { + + unreclaimableSize += imageUsedSize(img, imgUniqueSizeMap, imageUsedbyCintainerMap, imageUsedbyActiveContainerMap) + + isParent, err := img.IsParent() + if err != nil { + return imageDiskUsage, err + } + parent, err := img.GetParent() + if err != nil { + return imageDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) + } + if isParent && parent != nil { + continue + } + numberOfImages++ + if _, isActive := imageUsedbyCintainerMap[img.ID()]; isActive { + numberOfActiveImages++ + } + + if !isParent { + size, err := img.Size(ctx) + if err != nil { + return imageDiskUsage, errors.Wrapf(err, "error getting disk usage of image %s", img.ID()) + } + sumSize += *size + } + + } + sumSizeStr := units.HumanSizeWithPrecision(float64(sumSize), 3) + reclaimable := sumSize - unreclaimableSize + if sumSize != 0 { + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) + } else { + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) + } + imageDiskUsage = systemDfDiskUsage{ + Type: "Images", + Total: numberOfImages, + Active: numberOfActiveImages, + Size: sumSizeStr, + Reclaimable: reclaimableStr, + } + return imageDiskUsage, nil +} + +func imageUsedSize(img *image.Image, imgUniqueSizeMap map[string]uint64, imageUsedbyCintainerMap map[string][]*libpod.Container, imageUsedbyActiveContainerMap map[string][]*libpod.Container) uint64 { + var usedSize uint64 + imgUnique := imgUniqueSizeMap[img.ID()] + if _, isCtrActive := imageUsedbyActiveContainerMap[img.ID()]; isCtrActive { + return imgUnique + } + containers := imageUsedbyCintainerMap[img.ID()] + for _, ctr := range containers { + if len(ctr.UserVolumes()) > 0 { + usedSize += imgUnique + return usedSize + } + } + return usedSize +} + +func imagesUsedbyCtr(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (map[string][]*libpod.Container, map[string][]*libpod.Container, error) { + imgCtrMap := make(map[string][]*libpod.Container) + imgActiveCtrMap := make(map[string][]*libpod.Container) + for _, ctr := range containers { + imgID, _ := ctr.Image() + imgCtrMap[imgID] = append(imgCtrMap[imgID], ctr) + if _, isActive := activeContainers[ctr.ID()]; isActive { + imgActiveCtrMap[imgID] = append(imgActiveCtrMap[imgID], ctr) + } + } + return imgCtrMap, imgActiveCtrMap, nil +} + +func getContainerDiskUsage(containers []*libpod.Container, activeContainers map[string]*libpod.Container) (systemDfDiskUsage, error) { + var ( + sumSize int64 + unreclaimableSize int64 + reclaimableStr string + ) + for _, ctr := range containers { + size, err := ctr.RWSize() + if err != nil { + return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) + } + sumSize += size + } + for _, activeCtr := range activeContainers { + size, err := activeCtr.RWSize() + if err != nil { + return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of active container %s", activeCtr.ID()) + } + unreclaimableSize += size + } + if sumSize == 0 { + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(0, 3), 0) + } else { + reclaimable := sumSize - unreclaimableSize + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) + } + containerDiskUsage := systemDfDiskUsage{ + Type: "Containers", + Total: len(containers), + Active: len(activeContainers), + Size: units.HumanSizeWithPrecision(float64(sumSize), 3), + Reclaimable: reclaimableStr, + } + return containerDiskUsage, nil +} + +func ctrIsActive(ctr *libpod.Container) (bool, error) { + state, err := ctr.State() + if err != nil { + return false, err + } + return state == libpod.ContainerStatePaused || state == libpod.ContainerStateRunning, nil +} + +func activeContainers(containers []*libpod.Container) (map[string]*libpod.Container, error) { + activeContainers := make(map[string]*libpod.Container) + for _, aCtr := range containers { + isActive, err := ctrIsActive(aCtr) + if err != nil { + return nil, err + } + if isActive { + activeContainers[aCtr.ID()] = aCtr + } + } + return activeContainers, nil +} + +func getVolumeDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (systemDfDiskUsage, error) { + var ( + sumSize int64 + unreclaimableSize int64 + reclaimableStr string + ) + for _, volume := range volumes { + size, err := volumeSize(volume) + if err != nil { + return systemDfDiskUsage{}, errors.Wrapf(err, "error getting size of volime %s", volume.Name()) + } + sumSize += size + if _, exist := volumeUsedByContainerMap[volume.Name()]; exist { + unreclaimableSize += size + } + } + reclaimable := sumSize - unreclaimableSize + if sumSize != 0 { + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 100*reclaimable/sumSize) + } else { + reclaimableStr = fmt.Sprintf("%s (%v%%)", units.HumanSizeWithPrecision(float64(reclaimable), 3), 0) + } + volumesDiskUsage := systemDfDiskUsage{ + Type: "Local Volumes", + Total: len(volumes), + Active: len(volumeUsedByContainerMap), + Size: units.HumanSizeWithPrecision(float64(sumSize), 3), + Reclaimable: reclaimableStr, + } + return volumesDiskUsage, nil +} + +func volumeUsedByContainer(containers []*libpod.Container) map[string][]*libpod.Container { + volumeUsedByContainerMap := make(map[string][]*libpod.Container) + for _, ctr := range containers { + + ctrVolumes := ctr.UserVolumes() + for _, ctrVolume := range ctrVolumes { + volumeUsedByContainerMap[ctrVolume] = append(volumeUsedByContainerMap[ctrVolume], ctr) + } + } + return volumeUsedByContainerMap +} + +func volumeSize(volume *libpod.Volume) (int64, error) { + var size int64 + err := filepath.Walk(volume.MountPoint(), func(path string, info os.FileInfo, err error) error { + if err == nil && !info.IsDir() { + size += info.Size() + } + return err + }) + return size, err +} + +func getImageVerboseDiskUsage(ctx context.Context, images []*image.Image, imagesUsedbyCtr map[string][]*libpod.Container) ([]imageVerboseDiskUsage, error) { + var imagesVerboseDiskUsage []imageVerboseDiskUsage + imgUniqueSizeMap, err := imageUniqueSize(ctx, images) + if err != nil { + return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting unique size of images") + } + for _, img := range images { + isParent, err := img.IsParent() + if err != nil { + return imagesVerboseDiskUsage, errors.Wrapf(err, "error checking if %s is a parent images", img.ID()) + } + parent, err := img.GetParent() + if err != nil { + return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting parent of image %s", img.ID()) + } + if isParent && parent != nil { + continue + } + size, err := img.Size(ctx) + if err != nil { + return imagesVerboseDiskUsage, errors.Wrapf(err, "error getting size of image %s", img.ID()) + } + numberOfContainers := 0 + if ctrs, exist := imagesUsedbyCtr[img.ID()]; exist { + numberOfContainers = len(ctrs) + } + var repo string + var tag string + if len(img.Names()) == 0 { + repo = "<none>" + tag = "<none>" + } + repopairs, err := image.ReposToMap([]string{img.Names()[0]}) + if err != nil { + logrus.Errorf("error finding tag/digest for %s", img.ID()) + } + for reponame, tags := range repopairs { + for _, tagname := range tags { + repo = reponame + tag = tagname + } + } + + imageVerbosedf := imageVerboseDiskUsage{ + Repository: repo, + Tag: tag, + ImageID: shortID(img.ID()), + Created: units.HumanDuration(time.Since((img.Created().Local()))) + " ago", + Size: units.HumanSizeWithPrecision(float64(*size), 3), + SharedSize: units.HumanSizeWithPrecision(float64(*size-imgUniqueSizeMap[img.ID()]), 3), + UniqueSize: units.HumanSizeWithPrecision(float64(imgUniqueSizeMap[img.ID()]), 3), + Containers: numberOfContainers, + } + imagesVerboseDiskUsage = append(imagesVerboseDiskUsage, imageVerbosedf) + } + return imagesVerboseDiskUsage, nil +} + +func getContainerVerboseDiskUsage(containers []*libpod.Container) (containersVerboseDiskUsage []containerVerboseDiskUsage, err error) { + for _, ctr := range containers { + imgID, _ := ctr.Image() + size, err := ctr.RWSize() + if err != nil { + return containersVerboseDiskUsage, errors.Wrapf(err, "error getting size of container %s", ctr.ID()) + } + state, err := ctr.State() + if err != nil { + return containersVerboseDiskUsage, errors.Wrapf(err, "error getting the state of container %s", ctr.ID()) + } + + ctrVerboseData := containerVerboseDiskUsage{ + ContainerID: shortID(ctr.ID()), + Image: shortImageID(imgID), + Command: strings.Join(ctr.Command(), " "), + LocalVolumes: len(ctr.UserVolumes()), + Size: units.HumanSizeWithPrecision(float64(size), 3), + Created: units.HumanDuration(time.Since(ctr.CreatedTime().Local())) + "ago", + Status: state.String(), + Names: ctr.Name(), + } + containersVerboseDiskUsage = append(containersVerboseDiskUsage, ctrVerboseData) + + } + return containersVerboseDiskUsage, nil +} + +func getVolumeVerboseDiskUsage(volumes []*libpod.Volume, volumeUsedByContainerMap map[string][]*libpod.Container) (volumesVerboseDiskUsage []volumeVerboseDiskUsage, err error) { + for _, vol := range volumes { + volSize, err := volumeSize(vol) + if err != nil { + return volumesVerboseDiskUsage, errors.Wrapf(err, "error getting size of volume %s", vol.Name()) + } + links := 0 + if linkCtr, exist := volumeUsedByContainerMap[vol.Name()]; exist { + links = len(linkCtr) + } + volumeVerboseData := volumeVerboseDiskUsage{ + VolumeName: vol.Name(), + Links: links, + Size: units.HumanSizeWithPrecision(float64(volSize), 3), + } + volumesVerboseDiskUsage = append(volumesVerboseDiskUsage, volumeVerboseData) + } + return volumesVerboseDiskUsage, nil +} + +func imagesVerboseOutput(ctx context.Context, metaData dfMetaData) error { + var imageVerboseHeader = map[string]string{ + "Repository": "REPOSITORY", + "Tag": "TAG", + "ImageID": "IMAGE ID", + "Created": "CREATED", + "Size": "SIZE", + "SharedSize": "SHARED SIZE", + "UniqueSize": "UNQUE SIZE", + "Containers": "CONTAINERS", + } + imagesVerboseDiskUsage, err := getImageVerboseDiskUsage(ctx, metaData.images, metaData.imagesUsedbyCtrMap) + if err != nil { + return errors.Wrapf(err, "error getting verbose output of images") + } + os.Stderr.WriteString("Images space usage:\n\n") + out := formats.StdoutTemplateArray{Output: systemDfImageVerboseDiskUsageToGeneric(imagesVerboseDiskUsage), Template: "table {{.Repository}}\t{{.Tag}}\t{{.ImageID}}\t{{.Created}}\t{{.Size}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}", Fields: imageVerboseHeader} + formats.Writer(out).Out() + return nil +} + +func containersVerboseOutput(ctx context.Context, metaData dfMetaData) error { + var containerVerboseHeader = map[string]string{ + "ContainerID": "CONTAINER ID ", + "Image": "IMAGE", + "Command": "COMMAND", + "LocalVolumes": "LOCAL VOLUMES", + "Size": "SIZE", + "Created": "CREATED", + "Status": "STATUS", + "Names": "NAMES", + } + containersVerboseDiskUsage, err := getContainerVerboseDiskUsage(metaData.containers) + if err != nil { + return errors.Wrapf(err, "error getting verbose output of containers") + } + os.Stderr.WriteString("\nContainers space usage:\n\n") + out := formats.StdoutTemplateArray{Output: systemDfContainerVerboseDiskUsageToGeneric(containersVerboseDiskUsage), Template: "table {{.ContainerID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.Created}}\t{{.Status}}\t{{.Names}}", Fields: containerVerboseHeader} + formats.Writer(out).Out() + return nil +} + +func volumesVerboseOutput(ctx context.Context, metaData dfMetaData) error { + var volumeVerboseHeader = map[string]string{ + "VolumeName": "VOLUME NAME", + "Links": "LINKS", + "Size": "SIZE", + } + volumesVerboseDiskUsage, err := getVolumeVerboseDiskUsage(metaData.volumes, metaData.volumeUsedByContainerMap) + if err != nil { + return errors.Wrapf(err, "error getting verbose ouput of volumes") + } + os.Stderr.WriteString("\nLocal Volumes space usage:\n\n") + out := formats.StdoutTemplateArray{Output: systemDfVolumeVerboseDiskUsageToGeneric(volumesVerboseDiskUsage), Template: "table {{.VolumeName}}\t{{.Links}}\t{{.Size}}", Fields: volumeVerboseHeader} + formats.Writer(out).Out() + return nil +} + +func verboseOutput(ctx context.Context, metaData dfMetaData) error { + if err := imagesVerboseOutput(ctx, metaData); err != nil { + return err + } + if err := containersVerboseOutput(ctx, metaData); err != nil { + return err + } + if err := volumesVerboseOutput(ctx, metaData); err != nil { + return err + } + return nil +} + +func systemDfDiskUsageToGeneric(diskUsages []systemDfDiskUsage) (out []interface{}) { + for _, usage := range diskUsages { + out = append(out, interface{}(usage)) + } + return out +} + +func systemDfImageVerboseDiskUsageToGeneric(diskUsages []imageVerboseDiskUsage) (out []interface{}) { + for _, usage := range diskUsages { + out = append(out, interface{}(usage)) + } + return out +} + +func systemDfContainerVerboseDiskUsageToGeneric(diskUsages []containerVerboseDiskUsage) (out []interface{}) { + for _, usage := range diskUsages { + out = append(out, interface{}(usage)) + } + return out +} + +func systemDfVolumeVerboseDiskUsageToGeneric(diskUsages []volumeVerboseDiskUsage) (out []interface{}) { + for _, usage := range diskUsages { + out = append(out, interface{}(usage)) + } + return out +} + +func shortImageID(id string) string { + const imageIDTruncLength int = 4 + if len(id) > imageIDTruncLength { + return id[:imageIDTruncLength] + } + return id +} diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index a823dcad1..624f24acb 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -23,6 +23,7 @@ var ( ` _pruneSystemCommand = &cobra.Command{ Use: "prune", + Args: noSubArgs, Short: "Remove unused data", Long: pruneSystemDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -35,6 +36,7 @@ var ( func init() { pruneSystemCommand.Command = _pruneSystemCommand + pruneSystemCommand.SetHelpTemplate(HelpTemplate()) pruneSystemCommand.SetUsageTemplate(UsageTemplate()) flags := pruneSystemCommand.Flags() flags.BoolVarP(&pruneSystemCommand.All, "all", "a", false, "Remove all unused data") diff --git a/cmd/podman/system_renumber.go b/cmd/podman/system_renumber.go index c8ce628b1..ed0b28a3c 100644 --- a/cmd/podman/system_renumber.go +++ b/cmd/podman/system_renumber.go @@ -18,6 +18,7 @@ var ( _renumberCommand = &cobra.Command{ Use: "renumber", + Args: noSubArgs, Short: "Migrate lock numbers", Long: renumberDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -30,6 +31,7 @@ var ( func init() { renumberCommand.Command = _renumberCommand + renumberCommand.SetHelpTemplate(HelpTemplate()) renumberCommand.SetUsageTemplate(UsageTemplate()) } diff --git a/cmd/podman/tag.go b/cmd/podman/tag.go index 98c6e3449..98d9a6856 100644 --- a/cmd/podman/tag.go +++ b/cmd/podman/tag.go @@ -10,7 +10,7 @@ import ( var ( tagCommand cliconfig.TagValues - tagDescription = "Adds one or more additional names to locally-stored image" + tagDescription = "Adds one or more additional names to locally-stored image." _tagCommand = &cobra.Command{ Use: "tag [flags] IMAGE TAG [TAG...]", Short: "Add an additional name to a local image", @@ -28,6 +28,7 @@ var ( func init() { tagCommand.Command = _tagCommand + tagCommand.SetHelpTemplate(HelpTemplate()) tagCommand.SetUsageTemplate(UsageTemplate()) } diff --git a/cmd/podman/top.go b/cmd/podman/top.go index cdf270fa7..2512631c1 100644 --- a/cmd/podman/top.go +++ b/cmd/podman/top.go @@ -18,23 +18,23 @@ func getDescriptorString() string { descriptors, err := libpod.GetContainerPidInformationDescriptors() if err == nil { return fmt.Sprintf(` -Format Descriptors: -%s`, strings.Join(descriptors, ",")) + Format Descriptors: + %s`, strings.Join(descriptors, ",")) } return "" } var ( topCommand cliconfig.TopValues - topDescription = fmt.Sprintf(`Display the running processes of the container. Specify format descriptors -to alter the output. You may run "podman top -l pid pcpu seccomp" to print -the process ID, the CPU percentage and the seccomp mode of each process of -the latest container. -%s -`, getDescriptorString()) + topDescription = fmt.Sprintf(`Similar to system "top" command. + + Specify format descriptors to alter the output. + + Running "podman top -l pid pcpu seccomp" will print the process ID, the CPU percentage and the seccomp mode of each process of the latest container. +%s`, getDescriptorString()) _topCommand = &cobra.Command{ - Use: "top [flags] CONTAINER [FORMAT-DESCRIPTIOS]", + Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS]", Short: "Display the running processes of a container", Long: topDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -50,6 +50,7 @@ the latest container. func init() { topCommand.Command = _topCommand + topCommand.SetHelpTemplate(HelpTemplate()) topCommand.SetUsageTemplate(UsageTemplate()) flags := topCommand.Flags() flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "") @@ -108,7 +109,7 @@ func topCmd(c *cliconfig.TopValues) error { if err != nil { return err } - became, ret, err := rootless.JoinNS(uint(pid)) + became, ret, err := rootless.JoinNS(uint(pid), 0) if err != nil { return err } diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go new file mode 100644 index 000000000..c56e35aef --- /dev/null +++ b/cmd/podman/tree.go @@ -0,0 +1,190 @@ +package main + +import ( + "context" + "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/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + middleItem = "├── " + continueItem = "│ " + lastItem = "└── " +) + +var ( + treeCommand cliconfig.TreeValues + + treeDescription = "Prints layer hierarchy of an image in a tree format" + _treeCommand = &cobra.Command{ + Use: "tree [flags] IMAGE", + Short: treeDescription, + Long: treeDescription, + RunE: func(cmd *cobra.Command, args []string) error { + treeCommand.InputArgs = args + treeCommand.GlobalFlags = MainGlobalOpts + return treeCmd(&treeCommand) + }, + Example: "podman image tree alpine:latest", + } +) + +func init() { + treeCommand.Command = _treeCommand + treeCommand.SetUsageTemplate(UsageTemplate()) + 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 { + return errors.Errorf("an image name must be specified") + } + if len(args) > 1 { + return errors.Errorf("you must provide at most 1 argument") + } + + runtime, err := libpodruntime.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]) + if err != nil { + return err + } + + // 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(), + } + + size, err := img.Size(context.Background()) + if err != nil { + return errors.Wrapf(err, "error while retriving image size") + } + 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 { + // 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) + + } else { + // fill imageInfo with layers associated with image. + // 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) + } + if err := buildImageHierarchyMap(imageInfo, layerMap, ll.ParentID); err != nil { + return err + } + + imageInfo.layers = append(imageInfo.layers, *ll) + return nil +} + +// Stores all children layers which are created using given Image. +// Layers are stored as follows +// (Start)TopLayer->...intermediate Child Layer(s)-> Child TopLayer(End) +// (Forks)... intermediate Child Layer(s) -> Child Top Layer(End) +func printImageChildren(layerMap map[string]*image.LayerInfo, layerID string, prefix string, last bool) error { + if layerID == "" { + return nil + } + ll, ok := layerMap[layerID] + if !ok { + return fmt.Errorf("lookup error: layerid %s, not found", layerID) + } + fmt.Printf(prefix) + + //initialize intend with middleItem to reduce middleItem checks. + intend := middleItem + if !last { + // add continueItem i.e. '|' for next iteration prefix + prefix = prefix + continueItem + } else if len(ll.ChildID) > 1 || len(ll.ChildID) == 0 { + // The above condition ensure, alignment happens for node, which has more then 1 childern. + // If node is last in printing hierarchy, it should not be printed as middleItem i.e. ├── + intend = lastItem + prefix = prefix + " " + } + + var tags string + if len(ll.RepoTags) > 0 { + tags = fmt.Sprintf(" Top Layer of: %s", ll.RepoTags) + } + fmt.Printf("%sID: %s Size: %7v%s\n", intend, ll.ID[:12], units.HumanSizeWithPrecision(float64(ll.Size), 4), tags) + for count, childID := range ll.ChildID { + if err := printImageChildren(layerMap, childID, prefix, (count == len(ll.ChildID)-1)); err != nil { + return err + } + } + return nil +} + +// prints the layers info of image +func printImageHierarchy(imageInfo *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 { + 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/trust.go b/cmd/podman/trust.go index 8b02dcdc6..0a79e1570 100644 --- a/cmd/podman/trust.go +++ b/cmd/podman/trust.go @@ -6,16 +6,21 @@ import ( ) var ( + trustDescription = `Manages which registries you trust as a source of container images based on its location. + + The location is determined by the transport and the registry host of the image. Using this container image docker://docker.io/library/busybox as an example, docker is the transport and docker.io is the registry host.` trustCommand = cliconfig.PodmanCommand{ Command: &cobra.Command{ Use: "trust", Short: "Manage container image trust policy", - Long: "podman image trust command", + Long: trustDescription, + RunE: commandRunE(), }, } ) func init() { + trustCommand.SetHelpTemplate(HelpTemplate()) trustCommand.SetUsageTemplate(UsageTemplate()) trustCommand.AddCommand(getTrustSubCommands()...) imageCommand.AddCommand(trustCommand.Command) diff --git a/cmd/podman/trust_set_show.go b/cmd/podman/trust_set_show.go index 746854249..d7a4ea6d6 100644 --- a/cmd/podman/trust_set_show.go +++ b/cmd/podman/trust_set_show.go @@ -7,9 +7,9 @@ import ( "sort" "strings" + "github.com/containers/buildah/pkg/formats" "github.com/containers/image/types" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/trust" @@ -50,8 +50,10 @@ var ( func init() { setTrustCommand.Command = _setTrustCommand + setTrustCommand.SetHelpTemplate(HelpTemplate()) setTrustCommand.SetUsageTemplate(UsageTemplate()) showTrustCommand.Command = _showTrustCommand + showTrustCommand.SetHelpTemplate(HelpTemplate()) showTrustCommand.SetUsageTemplate(UsageTemplate()) setFlags := setTrustCommand.Flags() setFlags.StringVar(&setTrustCommand.PolicyPath, "policypath", "", "") diff --git a/cmd/podman/umount.go b/cmd/podman/umount.go index 48c97fa31..a938c7c38 100644 --- a/cmd/podman/umount.go +++ b/cmd/podman/umount.go @@ -14,12 +14,11 @@ import ( var ( umountCommand cliconfig.UmountValues - description = ` -Container storage increments a mount counter each time a container is mounted. -When a container is unmounted, the mount counter is decremented and the -container's root filesystem is physically unmounted only when the mount -counter reaches zero indicating no other processes are using the mount. -An unmount can be forced with the --force flag. + description = `Container storage increments a mount counter each time a container is mounted. + + When a container is unmounted, the mount counter is decremented. The container's root filesystem is physically unmounted only when the mount counter reaches zero indicating no other processes are using the mount. + + An unmount can be forced with the --force flag. ` _umountCommand = &cobra.Command{ Use: "umount [flags] CONTAINER [CONTAINER...]", @@ -32,7 +31,7 @@ An unmount can be forced with the --force flag. return umountCmd(&umountCommand) }, Args: func(cmd *cobra.Command, args []string) error { - return checkAllAndLatest(cmd, args, true) + return checkAllAndLatest(cmd, args, false) }, Example: `podman umount ctrID podman umount ctrID1 ctrID2 ctrID3 @@ -42,6 +41,7 @@ An unmount can be forced with the --force flag. func init() { umountCommand.Command = _umountCommand + umountCommand.SetHelpTemplate(HelpTemplate()) umountCommand.SetUsageTemplate(UsageTemplate()) flags := umountCommand.Flags() flags.BoolVarP(&umountCommand.All, "all", "a", false, "Umount all of the currently mounted containers") diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 58fd19fe1..0c52a2443 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -15,12 +15,8 @@ import ( var ( unpauseCommand cliconfig.UnpauseValues - unpauseDescription = ` - podman unpause - - Unpauses one or more running containers. The container name or ID can be used. -` - _unpauseCommand = &cobra.Command{ + unpauseDescription = `Unpauses one or more previously paused containers. The container name or ID can be used.` + _unpauseCommand = &cobra.Command{ Use: "unpause [flags] CONTAINER [CONTAINER...]", Short: "Unpause the processes in one or more containers", Long: unpauseDescription, @@ -36,6 +32,7 @@ var ( func init() { unpauseCommand.Command = _unpauseCommand + unpauseCommand.SetHelpTemplate(HelpTemplate()) unpauseCommand.SetUsageTemplate(UsageTemplate()) flags := unpauseCommand.Flags() flags.BoolVarP(&unpauseCommand.All, "all", "a", false, "Unpause all paused containers") diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index 4ec0f8a13..45d081512 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -200,35 +200,6 @@ func getPodsFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpo return pods, lastError } -func getVolumesFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpod.Volume, error) { - args := c.InputArgs - var ( - vols []*libpod.Volume - lastError error - err error - ) - - if c.Bool("all") { - vols, err = r.Volumes() - if err != nil { - return nil, errors.Wrapf(err, "unable to get all volumes") - } - } - - for _, i := range args { - vol, err := r.GetVolume(i) - if err != nil { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to find volume %s", i) - continue - } - vols = append(vols, vol) - } - return vols, 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.go b/cmd/podman/varlink.go index f19d03885..787ad01cd 100644 --- a/cmd/podman/varlink.go +++ b/cmd/podman/varlink.go @@ -18,10 +18,9 @@ import ( var ( varlinkCommand cliconfig.VarlinkValues - varlinkDescription = ` - podman varlink + varlinkDescription = `Run varlink interface. Podman varlink listens on the specified unix domain socket for incoming connects. - run varlink interface + Tools speaking varlink protocol can remotely manage pods, containers and images. ` _varlinkCommand = &cobra.Command{ Use: "varlink [flags] URI", @@ -39,6 +38,7 @@ var ( func init() { varlinkCommand.Command = _varlinkCommand + varlinkCommand.SetHelpTemplate(HelpTemplate()) varlinkCommand.SetUsageTemplate(UsageTemplate()) flags := varlinkCommand.Flags() flags.Int64VarP(&varlinkCommand.Timeout, "timeout", "t", 1000, "Time until the varlink session expires in milliseconds. Use 0 to disable the timeout") @@ -49,6 +49,9 @@ func varlinkCmd(c *cliconfig.VarlinkValues) error { if len(args) < 1 { return errors.Errorf("you must provide a varlink URI") } + if len(args) > 1 { + return errors.Errorf("too many arguments. Requires exactly 1") + } timeout := time.Duration(c.Timeout) * time.Millisecond // Create a single runtime for varlink diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 618af3481..ad2de56f8 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -19,6 +19,14 @@ type StringResponse ( message: string ) +type LogLine ( + device: string, + parseLogType : string, + time: string, + msg: string, + cid: string +) + # ContainerChanges describes the return struct for ListContainerChanges type ContainerChanges ( changed: []string, @@ -435,6 +443,23 @@ type Runlabel( opts: [string]string ) +# Event describes a libpod struct +type Event( + # TODO: make status and type a enum at some point? + # id is the container, volume, pod, image ID + id: string, + # image is the image name where applicable + image: string, + # name is the name of the pod, container, image + name: string, + # status describes the event that happened (i.e. create, remove, ...) + status: string, + # time the event happened + time: string, + # type describes object the event happened with (image, container...) + type: string +) + # GetVersion returns version and build information of the podman service method GetVersion() -> ( version: string, @@ -459,6 +484,11 @@ method ListContainers() -> (containers: []Container) # [InspectContainer](#InspectContainer). method GetContainer(id: string) -> (container: Container) +# GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of +# container names. The definition of latest container means the latest by creation date. In a multi- +# 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. @@ -500,6 +530,8 @@ method ListContainerProcesses(name: string, opts: []string) -> (container: []str # capability of varlink if the client invokes it. method GetContainerLogs(name: string) -> (container: []string) +method GetContainersLogs(names: []string, follow: bool, latest: bool, since: string, tail: int, timestamps: bool) -> (log: LogLine) + # ListContainerChanges takes a name or ID of a container and returns changes between the container and # its base image. It returns a struct of changed, deleted, and added path names. method ListContainerChanges(name: string) -> (container: ContainerChanges) @@ -544,6 +576,10 @@ method ExportContainer(name: string, path: string) -> (tarfile: string) # ~~~ method GetContainerStats(name: string) -> (container: ContainerStats) +# GetContainerStatsWithHistory takes a previous set of container statistics and uses libpod functions +# to calculate the containers statistics based on current and previous measurements. +method GetContainerStatsWithHistory(previousStats: ContainerStats) -> (container: ContainerStats) + # This method has not be implemented yet. # method ResizeContainerTty() -> (notimplemented: NotImplemented) @@ -612,10 +648,10 @@ method UnpauseContainer(name: string) -> (container: string) # ~~~ method GetAttachSockets(name: string) -> (sockets: Sockets) -# WaitContainer takes the name or ID of a container and waits until the container stops. Upon stopping, the return -# code of the container is returned. If the container container cannot be found by ID or name, -# a [ContainerNotFound](#ContainerNotFound) error is returned. -method WaitContainer(name: string) -> (exitcode: int) +# WaitContainer takes the name or ID of a container and waits the given interval in milliseconds until the container +# stops. Upon stopping, the return code of the container is returned. If the container container cannot be found by ID +# or name, a [ContainerNotFound](#ContainerNotFound) error is returned. +method WaitContainer(name: string, interval: int) -> (exitcode: int) # RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean # indicating whether to remove builtin volumes. Upon successful removal of the @@ -647,7 +683,7 @@ method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (conta method DeleteStoppedContainers() -> (containers: []string) # ListImages returns information about the images that are currently in storage. -# See also [InspectImage](InspectImage). +# See also [InspectImage](#InspectImage). method ListImages() -> (images: []Image) # GetImage returns information about a single image in storage. @@ -947,8 +983,7 @@ method RemovePod(name: string, force: bool) -> (pod: string) # This method has not be implemented yet. # method WaitPod() -> (notimplemented: NotImplemented) -# This method has not been implemented yet. -# method TopPod() -> (notimplemented: NotImplemented) +method TopPod(pod: string, latest: bool, descriptors: []string) -> (stats: []string) # GetPodStats takes the name or ID of a pod and returns a pod name and slice of ContainerStats structure which # contains attributes like memory and cpu usage. If the pod cannot be found, a [PodNotFound](#PodNotFound) @@ -1076,7 +1111,7 @@ method ContainerArtifacts(name: string, artifactName: string) -> (config: string # ContainerInspectData returns a container's inspect data in string form. This call is for # development of Podman only and generally should not be used. -method ContainerInspectData(name: string) -> (config: string) +method ContainerInspectData(name: string, size: bool) -> (config: string) # ContainerStateData returns a container's state config in string form. This call is for # development of Podman only and generally should not be used. @@ -1115,6 +1150,9 @@ method GetPodsByContext(all: bool, latest: bool, args: []string) -> (pods: []str # LoadImage allows you to load an image into local storage from a tarball. method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) -> (reply: MoreResponse) +# GetEvents returns known libpod events filtered by the options provided. +method GetEvents(filter: []string, since: string, until: string) -> (events: Event) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (id: string, reason: string) @@ -1144,3 +1182,6 @@ error ErrorOccurred (reason: string) # RuntimeErrors generally means a runtime could not be found or gotten. error RuntimeError (reason: string) + +# The Podman endpoint requires that you use a streaming connection. +error WantsMoreRequired (reason: string) diff --git a/cmd/podman/version.go b/cmd/podman/version.go index c65ba94f9..31b0b8e82 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -3,11 +3,12 @@ package main import ( "fmt" "os" + "strings" "text/tabwriter" "time" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -17,6 +18,7 @@ var ( versionCommand cliconfig.VersionValues _versionCommand = &cobra.Command{ Use: "version", + Args: noSubArgs, Short: "Display the Podman Version Information", RunE: func(cmd *cobra.Command, args []string) error { versionCommand.InputArgs = args @@ -42,6 +44,9 @@ func versionCmd(c *cliconfig.VersionValues) error { versionOutputFormat := c.Format if versionOutputFormat != "" { + if strings.Join(strings.Fields(versionOutputFormat), "") == "{{json.}}" { + versionOutputFormat = formats.JSONString + } var out formats.Writer switch versionOutputFormat { case formats.JSONString: diff --git a/cmd/podman/volume.go b/cmd/podman/volume.go index 8a8664151..2a071d0c7 100644 --- a/cmd/podman/volume.go +++ b/cmd/podman/volume.go @@ -5,15 +5,14 @@ import ( "github.com/spf13/cobra" ) -var volumeDescription = `Manage volumes. - -Volumes are created in and can be shared between containers.` +var volumeDescription = `Volumes are created in and can be shared between containers.` var volumeCommand = cliconfig.PodmanCommand{ Command: &cobra.Command{ Use: "volume", Short: "Manage volumes", Long: volumeDescription, + RunE: commandRunE(), }, } var volumeSubcommands = []*cobra.Command{ diff --git a/cmd/podman/volume_create.go b/cmd/podman/volume_create.go index 96b2ed8c7..8f6237272 100644 --- a/cmd/podman/volume_create.go +++ b/cmd/podman/volume_create.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -11,11 +12,7 @@ import ( var ( volumeCreateCommand cliconfig.VolumeCreateValues - volumeCreateDescription = ` -podman volume create - -Creates a new volume. If using the default driver, "local", the volume will -be created at.` + volumeCreateDescription = `If using the default driver, "local", the volume will be created on the host in the volumes directory under container storage.` _volumeCreateCommand = &cobra.Command{ Use: "create [flags] [NAME]", @@ -34,6 +31,7 @@ be created at.` func init() { volumeCreateCommand.Command = _volumeCreateCommand + volumeCommand.SetHelpTemplate(HelpTemplate()) volumeCreateCommand.SetUsageTemplate(UsageTemplate()) flags := volumeCreateCommand.Flags() flags.StringVar(&volumeCreateCommand.Driver, "driver", "", "Specify volume driver name (default local)") @@ -53,12 +51,12 @@ func volumeCreateCmd(c *cliconfig.VolumeCreateValues) error { return errors.Errorf("too many arguments, create takes at most 1 argument") } - labels, err := getAllLabels([]string{}, c.Label) + labels, err := shared.GetAllLabels([]string{}, c.Label) if err != nil { return errors.Wrapf(err, "unable to process labels") } - opts, err := getAllLabels([]string{}, c.Opt) + opts, err := shared.GetAllLabels([]string{}, c.Opt) if err != nil { return errors.Wrapf(err, "unable to process options") } diff --git a/cmd/podman/volume_inspect.go b/cmd/podman/volume_inspect.go index 8add7a375..fdd8b5b0b 100644 --- a/cmd/podman/volume_inspect.go +++ b/cmd/podman/volume_inspect.go @@ -9,12 +9,9 @@ import ( var ( volumeInspectCommand cliconfig.VolumeInspectValues - volumeInspectDescription = ` -podman volume inspect + volumeInspectDescription = `Display detailed information on one or more volumes. -Display detailed information on one or more volumes. Can change the format -from JSON to a Go template. -` + Use a Go template to change the format from JSON.` _volumeInspectCommand = &cobra.Command{ Use: "inspect [flags] VOLUME [VOLUME...]", Short: "Display detailed information on one or more volumes", @@ -32,6 +29,7 @@ from JSON to a Go template. func init() { volumeInspectCommand.Command = _volumeInspectCommand + volumeInspectCommand.SetHelpTemplate(HelpTemplate()) volumeInspectCommand.SetUsageTemplate(UsageTemplate()) flags := volumeInspectCommand.Flags() flags.BoolVarP(&volumeInspectCommand.All, "all", "a", false, "Inspect all volumes") diff --git a/cmd/podman/volume_ls.go b/cmd/podman/volume_ls.go index 5adfc1e91..2f35462a3 100644 --- a/cmd/podman/volume_ls.go +++ b/cmd/podman/volume_ls.go @@ -4,8 +4,8 @@ import ( "reflect" "strings" + "github.com/containers/buildah/pkg/formats" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -44,11 +44,11 @@ var ( podman volume ls List all available volumes. The output of the volumes can be filtered -and the output format can be changed to JSON or a user specified Go template. -` +and the output format can be changed to JSON or a user specified Go template.` _volumeLsCommand = &cobra.Command{ Use: "ls", Aliases: []string{"list"}, + Args: noSubArgs, Short: "List volumes", Long: volumeLsDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -61,6 +61,7 @@ and the output format can be changed to JSON or a user specified Go template. func init() { volumeLsCommand.Command = _volumeLsCommand + volumeLsCommand.SetHelpTemplate(HelpTemplate()) volumeLsCommand.SetUsageTemplate(UsageTemplate()) flags := volumeLsCommand.Flags() @@ -76,10 +77,6 @@ func volumeLsCmd(c *cliconfig.VolumeLsValues) error { } defer runtime.Shutdown(false) - if len(c.InputArgs) > 0 { - return errors.Errorf("too many arguments, ls takes no arguments") - } - opts := volumeLsOptions{ Quiet: c.Quiet, } diff --git a/cmd/podman/volume_prune.go b/cmd/podman/volume_prune.go index 1f7931aa4..70ba506e7 100644 --- a/cmd/podman/volume_prune.go +++ b/cmd/podman/volume_prune.go @@ -16,14 +16,13 @@ import ( var ( volumePruneCommand cliconfig.VolumePruneValues - volumePruneDescription = ` -podman volume prune + volumePruneDescription = `Volumes that are not currently owned by a container will be removed. -Remove all unused volumes. Will prompt for confirmation if not -using force. -` + The command prompts for confirmation which can be overridden with the --force flag. + Note all data will be destroyed.` _volumePruneCommand = &cobra.Command{ Use: "prune", + Args: noSubArgs, Short: "Remove all unused volumes", Long: volumePruneDescription, RunE: func(cmd *cobra.Command, args []string) error { @@ -36,6 +35,7 @@ using force. func init() { volumePruneCommand.Command = _volumePruneCommand + volumePruneCommand.SetHelpTemplate(HelpTemplate()) volumePruneCommand.SetUsageTemplate(UsageTemplate()) flags := volumePruneCommand.Flags() diff --git a/cmd/podman/volume_rm.go b/cmd/podman/volume_rm.go index 73b1a6668..8c6d5e97a 100644 --- a/cmd/podman/volume_rm.go +++ b/cmd/podman/volume_rm.go @@ -11,13 +11,9 @@ import ( var ( volumeRmCommand cliconfig.VolumeRmValues - volumeRmDescription = ` -podman volume rm + volumeRmDescription = `Remove one or more existing volumes. -Remove one or more existing volumes. Will only remove volumes that are -not being used by any containers. To remove the volumes anyways, use the ---force flag. -` + By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.` _volumeRmCommand = &cobra.Command{ Use: "rm [flags] VOLUME [VOLUME...]", Aliases: []string{"remove"}, @@ -36,6 +32,7 @@ not being used by any containers. To remove the volumes anyways, use the func init() { volumeRmCommand.Command = _volumeRmCommand + volumeRmCommand.SetHelpTemplate(HelpTemplate()) volumeRmCommand.SetUsageTemplate(UsageTemplate()) flags := volumeRmCommand.Flags() flags.BoolVarP(&volumeRmCommand.All, "all", "a", false, "Remove all volumes") diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go index 9df2e3208..4449898a0 100644 --- a/cmd/podman/wait.go +++ b/cmd/podman/wait.go @@ -2,11 +2,11 @@ package main import ( "fmt" - "os" + "reflect" "time" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -14,10 +14,7 @@ import ( var ( waitCommand cliconfig.WaitValues - waitDescription = ` - podman wait - - Block until one or more containers stop and then print their exit codes + waitDescription = `Block until one or more containers stop and then print their exit codes. ` _waitCommand = &cobra.Command{ Use: "wait [flags] CONTAINER [CONTAINER...]", @@ -36,6 +33,7 @@ var ( func init() { waitCommand.Command = _waitCommand + waitCommand.SetHelpTemplate(HelpTemplate()) waitCommand.SetUsageTemplate(UsageTemplate()) flags := waitCommand.Flags() flags.UintVarP(&waitCommand.Interval, "interval", "i", 250, "Milliseconds to wait before polling for completion") @@ -49,43 +47,36 @@ func waitCmd(c *cliconfig.WaitValues) error { return errors.Errorf("you must provide at least one container name or id") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + if c.Interval == 0 { + return errors.Errorf("interval must be greater then 0") + } + interval := time.Duration(c.Interval) * time.Millisecond + + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { - return errors.Wrapf(err, "error creating libpod runtime") + return errors.Wrapf(err, "error creating runtime") } defer runtime.Shutdown(false) + ok, failures, err := runtime.WaitOnContainers(getContext(), c, interval) if err != nil { - return errors.Wrapf(err, "could not get config") + return err } - var lastError error - if c.Latest { - latestCtr, err := runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to wait on latest container") - } - args = append(args, latestCtr.ID()) + for _, id := range ok { + fmt.Println(id) } - for _, container := range args { - ctr, err := runtime.LookupContainer(container) - if err != nil { - return errors.Wrapf(err, "unable to find container %s", container) - } - if c.Interval == 0 { - return errors.Errorf("interval must be greater then 0") - } - returnCode, err := ctr.WaitWithInterval(time.Duration(c.Interval) * time.Millisecond) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "failed to wait for the container %v", container) - } else { - fmt.Println(returnCode) + if len(failures) > 0 { + keys := reflect.ValueOf(failures).MapKeys() + lastKey := keys[len(keys)-1].String() + lastErr := failures[lastKey] + delete(failures, lastKey) + + for _, err := range failures { + outputError(err) } + return lastErr } - - return lastError + return nil } diff --git a/commands.md b/commands.md index 3fd27ad5d..156a1cdf6 100644 --- a/commands.md +++ b/commands.md @@ -1,82 +1,85 @@ ![PODMAN logo](logo/podman-logo-source.svg) + # libpod - library for running OCI-based containers in Pods ## Podman Commands -| Command | Description | Demo| -| :------------------------------------------------------- | :------------------------------------------------------------------------ | :----| -| [podman(1)](/docs/podman.1.md) | Simple management tool for pods and images || -| [podman-attach(1)](/docs/podman-attach.1.md) | Attach to a running container |[![...](/docs/play.png)](https://asciinema.org/a/XDlocUrHVETFECg4zlO9nBbLf)| -| [podman-build(1)](/docs/podman-build.1.md) | Build an image using instructions from Dockerfiles || -| [podman-commit(1)](/docs/podman-commit.1.md) | Create new image based on the changed container || -| [podman-container(1)](/docs/podman-container.1.md) | Manage Containers || -| [podman-container-checkpoint(1)](/docs/podman-container-checkpoint.1.md) | Checkpoints one or more running containers || -| [podman-container-cleanup(1)](/docs/podman-container-cleanup.1.md) | Cleanup Container storage and networks || -| [podman-container-exists(1)](/docs/podman-container-exists.1.md) | Check if an container exists in local storage || -| [podman-container-prune(1)](/docs/podman-container-prune.1.md) | Remove all stopped containers || -| [podman-container-refresh(1)](/docs/podman-container-refresh.1.md) | Refresh all containers state in database || -| [podman-container-restore(1)](/docs/podman-container-restore.1.md) | Restores one or more running containers || -| [podman-container-runlabel(1)](/docs/podman-container-runlabel.1.md) | Execute Image Label Method || -| [podman-cp(1)](/docs/podman-cp.1.md) | Copy files/folders between a container and the local filesystem || -| [podman-create(1)](/docs/podman-create.1.md) | Create a new container || -| [podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem |[![...](/docs/play.png)](https://asciinema.org/a/FXfWB9CKYFwYM4EfqW3NSZy1G)| -| [podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container -| [podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive |[![...](/docs/play.png)](https://asciinema.org/a/913lBIRAg5hK8asyIhhkQVLtV)| -| [podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | | -| [podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image |[![...](/docs/play.png)](https://asciinema.org/a/bCvUQJ6DkxInMELZdc5DinNSx)| -| [podman-image(1)](/docs/podman-image.1.md) | Manage Images|| -| [podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage|| -| [podman-image-prune(1)](/docs/podman-image-prune.1.md) | Remove all unused images|| -| [podman-image-sign(1)](/docs/podman-image-sign.1.md) | Create a signature for an image|| -| [podman-image-trust(1)](/docs/podman-image-trust.1.md) | Manage container registry image trust policy|| -| [podman-images(1)](/docs/podman-images.1.md) | List images in local storage |[![...](/docs/play.png)](https://asciinema.org/a/133649)| -| [podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image || -| [podman-info(1)](/docs/podman-info.1.md) | Display system information |[![...](/docs/play.png)](https://asciinema.org/a/yKbi5fQ89y5TJ8e1RfJd4ivTD)| -| [podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image |[![...](/docs/play.png)](https://asciinema.org/a/133418)| -| [podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/3jNos0A5yzO4hChu7ddKkUPw7)| -| [podman-load(1)](/docs/podman-load.1.md) | Load an image from docker archive or oci |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| -| [podman-login(1)](/docs/podman-login.1.md) | Login to a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| -| [podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry |[![...](/docs/play.png)](https://asciinema.org/a/oNiPgmfo1FjV2YdesiLpvihtV)| -| [podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| -| [podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/YSP6hNvZo0RGeMHDA97PhPAf3)| -| [podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| -| [podman-pod(1)](/docs/podman-pod.1.md) | Simple management tool for groups of containers, called pods || -| [podman-pod-create(1)](/docs/podman-pod-create.1.md) | Create a new pod || -| [podman-pod-inspect(1)](/docs/podman-pod-inspect.1.md) | Inspect a pod || -| [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. || -| [podman-pod-ps(1)](/docs/podman-pod-ps.1.md) | List the pods on the system || -| [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. || -| [podman-pod-restart](/docs/podman-pod-restart.1.md) | Restart one or more pods || -| [podman-pod-rm(1)](/docs/podman-pod-rm.1.md) | Remove one or more pods || -| [podman-pod-start(1)](/docs/podman-pod-start.1.md) | Start one or more pods || -| [podman-pod-stats(1)](/docs/podman-pod-stats.1.md) | Display a live stream of one or more pods' resource usage statistics || || -| [podman-pod-stop(1)](/docs/podman-pod-stop.1.md) | Stop one or more pods || -| [podman-pod-top(1)](/docs/podman-pod-top.1.md) | Display the running processes of a pod || -| [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. || -| [podman-port(1)](/docs/podman-port.1.md) | List port mappings for running containers |[![...](/docs/play.png)]()| -| [podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers |[![...](/docs/play.png)](https://asciinema.org/a/bbT41kac6CwZ5giESmZLIaTLR)| -| [podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry |[![...](/docs/play.png)](https://asciinema.org/a/lr4zfoynHJOUNu1KaXa1dwG2X)| -| [podman-push(1)](/docs/podman-push.1.md) | Push an image to a specified destination |[![...](/docs/play.png)](https://asciinema.org/a/133276)| -| [podman-restart](/docs/podman-restart.1.md) | Restarts one or more containers |[![...](/docs/play.png)](https://asciinema.org/a/jiqxJAxcVXw604xdzMLTkQvHM)| -| [podman-rm(1)](/docs/podman-rm.1.md) | Removes one or more containers |[![...](/docs/play.png)](https://asciinema.org/a/7EMk22WrfGtKWmgHJX9Nze1Qp)| -| [podman-rmi(1)](/docs/podman-rmi.1.md) | Removes one or more images |[![...](/docs/play.png)](https://asciinema.org/a/133799)| -| [podman-run(1)](/docs/podman-run.1.md) | Run a command in a container || -| [podman-runlabel(1)](/docs/podman-container-runlabel.1.md) | Executes the command of a container image's label || -| [podman-save(1)](/docs/podman-save.1.md) | Saves an image to an archive |[![...](/docs/play.png)](https://asciinema.org/a/kp8kOaexEhEa20P1KLZ3L5X4g)| -| [podman-search(1)](/docs/podman-search.1.md) | Search a registry for an image || -| [podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers -| [podman-stats(1)](/docs/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics|[![...](/docs/play.png)](https://asciinema.org/a/vfUPbAA5tsNWhsfB9p25T6xdr)| -| [podman-stop(1)](/docs/podman-stop.1.md) | Stops one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/KNRF9xVXeaeNTNjBQVogvZBcp)| -| [podman-system(1)](/docs/podman-system.1.md) | Manage podman || -| [podman-tag(1)](/docs/podman-tag.1.md) | Add an additional name to a local image |[![...](/docs/play.png)](https://asciinema.org/a/133803)| -| [podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container |[![...](/docs/play.png)](https://asciinema.org/a/5WCCi1LXwSuRbvaO9cBUYf3fk)| -| [podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem |[![...](/docs/play.png)](https://asciinema.org/a/MZPTWD5CVs3dMREkBxQBY9C5z)| -| [podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers |[![...](/docs/play.png)](https://asciinema.org/a/141292)| -| [podman-varlink(1)](/docs/podman-varlink.1.md) | Run the varlink backend || -| [podman-version(1)](/docs/podman-version.1.md) | Display the version information |[![...](/docs/play.png)](https://asciinema.org/a/mfrn61pjZT9Fc8L4NbfdSqfgu)| -| [podman-volume(1)](/docs/podman-volume.1.md) | Manage Volumes || -| [podman-volume-create(1)](/docs/podman-volume-create.1.md) | Create a volume || -| [podman-volume-inspect(1)](/docs/podman-volume-inspect.1.md) | Get detailed information on one or more volumes || -| [podman-volume-ls(1)](/docs/podman-volume-ls.1.md) | List all the available volumes || -| [podman-volume-rm(1)](/docs/podman-volume-rm.1.md) | Remove one or more volumes || -| [podman-volume-prune(1)](/docs/podman-volume-prune.1.md) | Remove all unused volumes || -| [podman-wait(1)](/docs/podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes |[![...](/docs/play.png)](https://asciinema.org/a/QNPGKdjWuPgI96GcfkycQtah0)| + +Command | Description | Demo +:----------------------------------------------------------------------- | :------------------------------------------------------------------------- | :-------------------------------------------------------------------------- +[podman(1)](/docs/podman.1.md) | Simple management tool for pods and images | +[podman-attach(1)](/docs/podman-attach.1.md) | Attach to a running container | +[podman-build(1)](/docs/podman-build.1.md) | Build an image using instructions from Dockerfiles | +[podman-commit(1)](/docs/podman-commit.1.md) | Create new image based on the changed container | +[podman-container(1)](/docs/podman-container.1.md) | Manage Containers | +[podman-container-checkpoint(1)](/docs/podman-container-checkpoint.1.md) | Checkpoints one or more running containers | +[podman-container-cleanup(1)](/docs/podman-container-cleanup.1.md) | Cleanup Container storage and networks | +[podman-container-exists(1)](/docs/podman-container-exists.1.md) | Check if an container exists in local storage | +[podman-container-prune(1)](/docs/podman-container-prune.1.md) | Remove all stopped containers | +[podman-container-refresh(1)](/docs/podman-container-refresh.1.md) | Refresh all containers state in database | +[podman-container-restore(1)](/docs/podman-container-restore.1.md) | Restores one or more running containers | +[podman-container-runlabel(1)](/docs/podman-container-runlabel.1.md) | Execute Image Label Method | +[podman-cp(1)](/docs/podman-cp.1.md) | Copy files/folders between a container and the local filesystem | +[podman-create(1)](/docs/podman-create.1.md) | Create a new container | +[podman-diff(1)](/docs/podman-diff.1.md) | Inspect changes on a container or image's filesystem | +[podman-events(1)](/docs/podman-events.1.md) | Monitor Podman events | +[podman-exec(1)](/docs/podman-exec.1.md) | Execute a command in a running container | +[podman-export(1)](/docs/podman-export.1.md) | Export container's filesystem contents as a tar archive | +[podman-generate(1)](/docs/podman-generate.1.md) | Generate structured output based on Podman containers and pods | +[podman-history(1)](/docs/podman-history.1.md) | Shows the history of an image | +[podman-image(1)](/docs/podman-image.1.md) | Manage Images | +[podman-image-exists(1)](/docs/podman-image-exists.1.md) | Check if an image exists in local storage | +[podman-image-prune(1)](/docs/podman-image-prune.1.md) | Remove all unused images | +[podman-image-sign(1)](/docs/podman-image-sign.1.md) | Create a signature for an image | +[podman-image-trust(1)](/docs/podman-image-trust.1.md) | Manage container registry image trust policy | +[podman-images(1)](/docs/podman-images.1.md) | List images in local storage | [![...](/docs/play.png)](https://asciinema.org/a/133649) +[podman-import(1)](/docs/podman-import.1.md) | Import a tarball and save it as a filesystem image | +[podman-info(1)](/docs/podman-info.1.md) | Display system information | +[podman-inspect(1)](/docs/podman-inspect.1.md) | Display the configuration of a container or image | [![...](/docs/play.png)](https://asciinema.org/a/133418) +[podman-kill(1)](/docs/podman-kill.1.md) | Kill the main process in one or more running containers | +[podman-load(1)](/docs/podman-load.1.md) | Load an image from a container image archive | +[podman-login(1)](/docs/podman-login.1.md) | Login to a container registry | +[podman-logout(1)](/docs/podman-logout.1.md) | Logout of a container registry | +[podman-logs(1)](/docs/podman-logs.1.md) | Display the logs of a container | +[podman-mount(1)](/docs/podman-mount.1.md) | Mount a working container's root filesystem | +[podman-pause(1)](/docs/podman-pause.1.md) | Pause one or more running containers | [![...](/docs/play.png)](https://asciinema.org/a/141292) +[podman-play(1)](/docs/podman-play.1.md) | Play pods and containers based on a structured input file | +[podman-pod(1)](/docs/podman-pod.1.md) | Simple management tool for groups of containers, called pods | +[podman-pod-create(1)](/docs/podman-pod-create.1.md) | Create a new pod | +[podman-pod-inspect(1)](/docs/podman-pod-inspect.1.md) | Inspect a pod | +[podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. | +[podman-pod-ps(1)](/docs/podman-pod-ps.1.md) | List the pods on the system | +[podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | +[podman-pod-restart](/docs/podman-pod-restart.1.md) | Restart one or more pods | +[podman-pod-rm(1)](/docs/podman-pod-rm.1.md) | Remove one or more pods | +[podman-pod-start(1)](/docs/podman-pod-start.1.md) | Start one or more pods | +[podman-pod-stats(1)](/docs/podman-pod-stats.1.md) | Display a live stream of one or more pods' resource usage statistics | | | +[podman-pod-stop(1)](/docs/podman-pod-stop.1.md) | Stop one or more pods | +[podman-pod-top(1)](/docs/podman-pod-top.1.md) | Display the running processes of a pod | +[podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. | +[podman-port(1)](/docs/podman-port.1.md) | List port mappings for running containers | +[podman-ps(1)](/docs/podman-ps.1.md) | Prints out information about containers | +[podman-pull(1)](/docs/podman-pull.1.md) | Pull an image from a registry | +[podman-push(1)](/docs/podman-push.1.md) | Push an image to a specified destination | [![...](/docs/play.png)](https://asciinema.org/a/133276) +[podman-restart](/docs/podman-restart.1.md) | Restarts one or more containers | [![...](/docs/play.png)](https://asciinema.org/a/jiqxJAxcVXw604xdzMLTkQvHM) +[podman-rm(1)](/docs/podman-rm.1.md) | Removes one or more containers | +[podman-rmi(1)](/docs/podman-rmi.1.md) | Removes one or more images | +[podman-run(1)](/docs/podman-run.1.md) | Run a command in a container | +[podman-save(1)](/docs/podman-save.1.md) | Saves an image to an archive | +[podman-search(1)](/docs/podman-search.1.md) | Search a registry for an image | +[podman-start(1)](/docs/podman-start.1.md) | Starts one or more containers | +[podman-stats(1)](/docs/podman-stats.1.md) | Display a live stream of one or more containers' resource usage statistics | +[podman-stop(1)](/docs/podman-stop.1.md) | Stops one or more running containers | +[podman-system(1)](/docs/podman-system.1.md) | Manage podman | +[podman-tag(1)](/docs/podman-tag.1.md) | Add an additional name to a local image | [![...](/docs/play.png)](https://asciinema.org/a/133803) +[podman-top(1)](/docs/podman-top.1.md) | Display the running processes of a container | +[podman-umount(1)](/docs/podman-umount.1.md) | Unmount a working container's root filesystem | +[podman-unpause(1)](/docs/podman-unpause.1.md) | Unpause one or more running containers | [![...](/docs/play.png)](https://asciinema.org/a/141292) +[podman-varlink(1)](/docs/podman-varlink.1.md) | Run the varlink backend | +[podman-version(1)](/docs/podman-version.1.md) | Display the version information | +[podman-volume(1)](/docs/podman-volume.1.md) | Manage Volumes | +[podman-volume-create(1)](/docs/podman-volume-create.1.md) | Create a volume | +[podman-volume-inspect(1)](/docs/podman-volume-inspect.1.md) | Get detailed information on one or more volumes | +[podman-volume-ls(1)](/docs/podman-volume-ls.1.md) | List all the available volumes | +[podman-volume-rm(1)](/docs/podman-volume-rm.1.md) | Remove one or more volumes | +[podman-volume-prune(1)](/docs/podman-volume-prune.1.md) | Remove all unused volumes | +[podman-wait(1)](/docs/podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes diff --git a/completions/bash/podman b/completions/bash/podman index 36ac27d52..dfa673481 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -888,6 +888,26 @@ _podman_container_wait() { _podman_wait } +_podman_healthcheck() { + local boolean_options=" + --help + -h + " + subcommands=" + run + " + __podman_subcommands "$subcommands $aliases" && return + + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + ;; + *) + COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) + ;; + esac +} + _podman_generate() { local boolean_options=" --help @@ -979,6 +999,24 @@ _podman_container() { esac } +_podman_system_df() { + local options_with_args=" + --format + --verbose + " + local boolean_options=" + -h + --help + --verbose + -v + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac +} + _podman_system_info() { _podman_info } @@ -1009,6 +1047,7 @@ _podman_system() { -h " subcommands=" + df info prune " @@ -2308,6 +2347,15 @@ _podman_load() { _complete_ "$options_with_args" "$boolean_options" } +_podman_cp() { + local boolean_options=" + --help + -h + --extract + " + _complete_ "$boolean_options" +} + _podman_login() { local options_with_args=" --username @@ -2338,6 +2386,27 @@ _podman_logout() { _complete_ "$options_with_args" "$boolean_options" } +_podman_healtcheck_run() { + local options_with_args="" + + local boolean_options=" + -h + --help + " + + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + *) + COMPREPLY=( $( compgen -W " + $(__podman_containers --all) + " -- "$cur" ) ) + __ltrim_colon_completions "$cur" + ;; + esac +} + _podman_generate_kube() { local options_with_args="" @@ -2388,6 +2457,22 @@ _podman_play_kube() { esac } +_podman_events() { + local options_with_args=" + --help + --h + --filter + --format + --since + --until + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$options_with_args" -- "$cur")) + ;; + esac +} + _podman_container_runlabel() { local options_with_args=" --authfile @@ -2401,10 +2486,9 @@ _podman_container_runlabel() { --display --help -h - -p - --pull -q --quiet + --replace --tls-verify " @@ -2976,11 +3060,14 @@ _podman_podman() { build commit container + cp create diff + events exec export generate + healthcheck history image images diff --git a/completions/zsh/_podman b/completions/zsh/_podman new file mode 100644 index 000000000..530649c0c --- /dev/null +++ b/completions/zsh/_podman @@ -0,0 +1,385 @@ +#compdef podman + +# To get zsh to reread this file: unset -f _podman;rm -f ~/.zcompdump;compinit + +# On rereads, reset cache. (Not that the cacheing works, but some day it might) +unset -m '_podman_*' + +############################################################################### +# BEGIN 'podman help' parsers -- for options, subcommands, and usage + +# Run 'podman XX --help', set _podman_commands to a formatted list of cmds +_read_podman_commands() { + local line + + # Cache: the intention here is to run each 'podman help' only once. + # Unfortunately it doesn't seem to actually be working: even though + # I can see the var_ref in my shell, it's not visible here. + local _var_ref=_podman_commands_"${*// /_}" + typeset -ga _podman_commands + _podman_commands=(${(P)_var_ref}) + (( $#_podman_commands )) && return + + _call_program podman podman "$@" --help |\ + sed -n -e '0,/^Available Commands/d' -e '/^[A-Z]/q;p' |\ + sed -e 's/^ \+\([^ ]\+\) \+/\1:/' |\ + egrep . | while read line; do + _podman_commands+=($line) + done + + eval "typeset -ga $_var_ref" + eval "$_var_ref=(\$_podman_commands)" +} + +# Run 'podman XX --help', set _podman_flag_list to a formatted list +# of flag options for XX +_read_podman_flags() { + local line + + local _var_ref=_podman_flags_"${*// /_}" + eval "typeset -ga ${_var_ref}" + typeset -ga _podman_flag_list + _podman_flag_list=(${(P)_var_ref}) + (( $#_podman_flag_list )) && return + + # Extract the Flags; strip leading whitespace; pack '-f, --foo' + # as '-f,--foo' (no space); then add '=' to '--foo string'. + # The result will be, e.g. '-f,--foo=string Description of Option' + _call_program podman podman "$@" --help |\ + sed -n -e '0,/^Flags:/d' -e '/^$/q;p' |\ + sed -e 's/^ *//' -e 's/^\(-.,\) --/\1--/' |\ + sed -e 's/^\(-[^ ]\+\) \([^ ]\+\) /\1=\2 /' |\ + while read flags desc;do + # flags like --foo=string: split into --foo & string + local -a tmpa + local optval= + tmpa=(${(s.=.)flags}) + if [ -n "$tmpa[2]" ]; then + flags=$tmpa[1] + optval=$tmpa[2] + fi + + # 'podman attach --detach-keys' includes ']' in help msg + desc=${desc//\]/\\]} + + for flag in ${(s:,:)flags}; do + if [ -n "$optval" ]; then + _podman_flag_list+=("${flag}[$desc]:$(_podman_find_helper ${flags} ${optval} ${desc})") + else + _podman_flag_list+=("${flag}[$desc]") + fi + done + done + + eval "typeset -ga $_var_ref=(\$_podman_flag_list)" +} + +# Run 'podman XXX --help', set _podman_usage to the line after "Usage:" +_read_podman_usage() { + local _var_ref=_podman_usage_"${*// /_}" + typeset -ga _podman_usage + _podman_usage=${(P)_var_ref} + (( $#_podman_usage )) && return + + _podman_usage=$(_call_program podman podman "$@" --help |\ + grep -A1 'Usage:'|\ + tail -1 |\ + sed -e 's/^ *//') + + eval "typeset -ga $_var_ref" + eval "$_var_ref=\$_podman_usage" +} + +# END 'podman help' parsers +############################################################################### +# BEGIN custom helpers for individual option arguments + +# Find a zsh helper for a given flag or command-line option +_podman_find_helper() { + local flags=$1 + local optval=$2 + local desc=$3 + local helper= + + # Yes, this is a lot of hardcoding. IMHO it's still better than + # hardcoding every possible podman option. + # FIXME: there are many more options that could use helpers. + if expr "$desc" : ".*[Dd]irectory" >/dev/null; then + optval="directory" + helper="_files -/" + elif expr "$desc" : ".*[Pp]ath" >/dev/null; then + optval="path" + helper=_files + elif [ "$flags" = "--cgroup-manager" ]; then + optval="cgroup manager" + helper="(cgroupfs systemd)" + elif [ "$flags" = "--log-level" ]; then + optval="log level" + # 'Log messages above specified level: debug, ... (default "...")' + # Strip off the description and all 'default' strings + desc=${desc/Log*:/} # debug, info, ... (default "...") + desc=${(S)desc//\(*\)/} # debug, info, ... or panic + desc=${desc//,/} # debug info ... or panic + desc=${desc// or / } # debug info ... panic + desc=${desc// / } # collapse multiple spaces + # FIXME: how to present values _in order_, not sorted alphabetically? + helper="($desc)" + fi + echo "$optval:$helper" +} + +# END custom helpers for individual option arguments +############################################################################### +# BEGIN helpers for command-line args (containers, images) + +__podman_helper_generic() { + local expl line + local -a results + + local foo1=$1; shift + local name=$2; shift + + _call_program $foo1 podman "$@" |\ + while read line; do + results+=(${=line}) + done + + _wanted $foo1 expl $name compadd ${(u)results} +} + +_podman_helper_image() { + __podman_helper_generic podman-images 'images' \ + images --format '{{.ID}}\ {{.Repository}}:{{.Tag}}' +} + +# FIXME: at some point, distinguish between running & stopped containers +_podman_helper_container() { + __podman_helper_generic podman-containers 'containers' \ + ps -a --format '{{.Names}}\ {{.ID}}' +} + +_podman_helper_pod() { + __podman_helper_generic podman-pods 'pods' pod list --format '{{.Name}}' +} + +_podman_helper_volume() { + __podman_helper_generic podman-volumes 'volumes' volume ls --format '{{.Name}}' +} + +# Combinations. This one seen in diff & inspect +_podman_helper_container-or-image() { + _podman_helper_image + _podman_helper_container +} + +# Seen in generate-kube +_podman_helper_container-or-pod() { + _podman_helper_container + _podman_helper_pod +} + +# For top and pod-top +_podman_helper_format-descriptors() { + __podman_helper_generic top-format-descriptors 'format descriptors' \ + top --list-descriptors +} + +# for push, login/logout, and trust +# FIXME: some day, use this to define a helper for IMAGE-PATH (in 'pull') +_podman_helper_registry() { + local expl + local -a registries + + # Suggestions for improvement more than welcome. + python3 -c 'from configparser import ConfigParser;cp=ConfigParser();cp.read("/etc/containers/registries.conf");registries=eval(cp.get("registries.search","registries"));[print(r) for r in registries]' 2>/dev/null | while read line; do + registries+=($line) + done + + if (( $#registries )); then + _wanted podman-registry expl "registry" compadd ${(u)registries} + else + _hosts + fi +} + +# END helpers for command-line args +############################################################################### +# BEGIN figure out completion helpers for a given (sub)command + +# Read Usage string for this subcommand, set up helpers for its subargs +_set_up_podman_args() { + _read_podman_usage "$@" + + typeset -ga _podman_args=() + # E.g. 'podman exec [flags] CONTAINER [...' -> 'CONTAINER [....' + local usage_rhs=$(expr "$_podman_usage" : ".*\[flags\] \+\(.*\)") + + # e.g. podman pod ps which takes no further args + if [ -z "$usage_rhs" ]; then + return + fi + + # podman diff & inspect accept 'CONTAINER | IMAGE'; make into one keyword. + usage_rhs=${usage_rhs// | /-OR-} + + # Arg parsing. There are three possibilities in Usage messages: + # + # [IMAGE] - optional image arg (zero or one) + # IMAGE - exactly one image arg + # IMAGE [IMAGE...] - one or more image args + # and, theoretically: + # [IMAGE...] - zero or more? Haven't seen it in practice. Defer. + # + # For completion purposes, we only need to provide two options: + # one, or more than one? That is: continue offering completion + # suggestions after the first one? For that, we make two passes; + # in the first, mark an option as either '' (only one) or + + # Parse each command-line arg seen in usage message + local word + local -A _seen=() + for word in ${=usage_rhs}; do + local unbracketed=$(expr "$word" : "\[\(.*\)\]") + + if [ -n "$unbracketed" ]; then + # Remove all dots; assume(!?) that they'll all be at the end + unbracketed=${unbracketed//./} + + if (( $_seen[$unbracketed] )); then + # Is this the same word as the previous arg? + if expr "$_podman_args[-1]" : ":$unbracketed:" >/dev/null; then + # Yes. Make it '*:...' instead of ':...', indicating >1 + _podman_args[-1]="*$_podman_args[-1]" + fi + continue + fi + + word=$unbracketed + fi + + # As of 2019-03 all such instances are '[COMMAND [ARG...]]' and are, + # of course, at the end of the line. We can't offer completion for + # these, because the container will have different commands than + # the host system... but try anyway. + if [ "$word" = '[COMMAND' ]; then + # e.g. podman create, exec, run + _podman_args+=( + ":command: _command_names -e" + "*::arguments: _normal" + ) + return + fi + + # Look for an existing helper, e.g. IMAGE -> _podman_helper_image + local helper="_podman_helper_${(L)word}" + if (( $+functions[$helper] )); then + : + else + # No defined helper. Reset, but check for known expressions. + helper= + case "$word" in + KUBEFILE) helper='_files -g "*.y(|a)ml(-.)"' ;; + PATH) helper='_files' ;; + esac + fi + + # Another special case: 'top' actually takes multiple options + local multi= + if [ "$word" = "FORMAT-DESCRIPTORS" ]; then + multi='*' + fi + _podman_args+=("$multi:${(L)word}:$helper") + _seen[$word]=1 + done +} + +# For an endpoint command, i.e. not a subcommand. +_podman_terminus() { + typeset -A opt_args + typeset -ga _podman_flag_list + typeset -ga _podman_args + integer ret=1 + + # Find out what args it takes (e.g. image(s), container(s)) and see + # if we have helpers for them. + _set_up_podman_args "$@" + _arguments -C $_podman_flag_list $_podman_args && ret=0 + + return ret +} + +# END figure out completion helpers for a given (sub)command +################################################################################ +# BEGIN actual entry point + +# This is the main entry point; it's also where we (recursively) come in +# to handle nested subcommands such as 'podman container' or even 3-level +# ones like 'podman generate kube'. Nesting is complicated, so here's +# my best understanding as of 2019-03-12: +# +# Easy first: when you do "podman <TAB>" zsh calls us, we run 'podman --help', +# figure out the global options and subcommands, and run the magic _arguments +# command. That will offer those options/subcommands, and complete, etc. +# +# Where it gets harder is when you do "podman container mount <TAB>". +# zsh first calls us with words=(podman container mount) but we don't +# want all that full context yet! We want to go a piece at a time, +# handling 'container' first, then 'mount'; ending up with our +# final 'podman container mount --help' giving us suitable flags +# and no subcommands; from which we determine that it's a terminus +# and jump to a function that handles non-subcommand arguments. +# +# This is the closest I've yet come to understanding zsh completion; +# it is still incomplete and may in fact be incorrect. But it works +# better than anything I've played with so far. +_podman_subcommand() { + local curcontext="$curcontext" state line + typeset -A opt_args + integer ret=1 + + # Run 'podman --help' / 'podman system --help' for our context (initially + # empty, then possibly under subcommands); from those, get a list of + # flags appropriate for this context and, if applicable, subcommands. + _read_podman_flags "$@" + _read_podman_commands "$@" + + # Now, is this a sub-subcommand, or does it have args? + if (( $#_podman_commands )); then + # Subcommands required (podman, podman system, etc) + local cmd=${words// /_} + _arguments -C $_podman_flag_list \ + "(-): :->command" \ + "(-)*:: :->option-or-argument" \ + && ret=0 + + case $state in + (command) + _describe -t command "podman $* command" _podman_commands && ret=0 + ;; + (option-or-argument) + # I think this is when we have a _completed_ subcommand. + # Recurse back into here, offering options for that subcommand. + curcontext=${curcontext%:*:*}:podman-${words[1]}: + _podman_subcommand "$@" ${words[1]} && ret=0 + ;; + esac + else + # At a terminus, i.e. podman info, podman history; find out + # what args it takes. + _podman_terminus "$@" && ret=0 + fi + + return ret +} + +_podman() { + _podman_subcommand +} + +# Local Variables: +# mode: shell-script +# sh-indentation: 4 +# indent-tabs-mode: nil +# sh-basic-offset: 4 +# End: +# vim: ft=zsh sw=4 ts=4 et diff --git a/contrib/cirrus/README.md b/contrib/cirrus/README.md index 0d91301c6..0dabf5df6 100644 --- a/contrib/cirrus/README.md +++ b/contrib/cirrus/README.md @@ -33,6 +33,17 @@ task (pass or fail) is set based on the exit status of the last script to execut the vendor.conf, the code and the vendored packages in ./vendor are in sync at all times. +### ``meta`` Task + +***N/B: Steps below are performed by automation*** + +1. Launch a container built from definition in ``./contrib/imgts``. + +2. Update VM Image metadata to help track usage across all automation. + +4. Always exits successfully unless there's a major problem. + + ### ``testing`` Task ***N/B: Steps below are performed by automation*** @@ -52,6 +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 + +***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. + +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/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index 0fd86dfdc..58c8af289 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -19,7 +19,7 @@ case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in ubuntu-18) make install PREFIX=/usr ETCDIR=/etc make test-binaries - SKIP_USERNS=1 make localintegration GINKGOTIMEOUT=90m + SKIP_USERNS=1 make localintegration ;; fedora-29) ;& # Continue to the next item fedora-28) ;& diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh index 8be696933..e941610e2 100644 --- a/contrib/cirrus/lib.sh +++ b/contrib/cirrus/lib.sh @@ -53,6 +53,7 @@ show_env_vars() { echo " BUILDTAGS $BUILDTAGS BUILT_IMAGE_SUFFIX $BUILT_IMAGE_SUFFIX +ROOTLESS_USER $ROOTLESS_USER CI $CI CIRRUS_CI $CIRRUS_CI CI_NODE_INDEX $CI_NODE_INDEX @@ -100,6 +101,15 @@ clean_env() { unset -v UNSET_ENV_VARS $UNSET_ENV_VARS || true # don't fail on read-only } +die() { + req_env_var " + 1 $1 + 2 $2 + " + echo "$2" + exit $1 +} + # Return a GCE image-name compatible string representation of distribution name os_release_id() { eval "$(egrep -m 1 '^ID=' /etc/os-release | tr -d \' | tr -d \")" @@ -117,6 +127,15 @@ 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" } @@ -124,14 +143,14 @@ stub() { ircmsg() { req_env_var " CIRRUS_TASK_ID $CIRRUS_TASK_ID - 1 $1 + @ $@ " # Sometimes setup_environment.sh didn't run SCRIPT="$(dirname $0)/podbot.py" NICK="podbot_$CIRRUS_TASK_ID" NICK="${NICK:0:15}" # Any longer will break things set +e - $SCRIPT $NICK $1 + $SCRIPT $NICK $@ echo "Ignoring exit($?)" set -e } @@ -146,12 +165,57 @@ record_timestamp() { echo -e "BLEEEEEEEEEEP!\n." } -# Run sudo in directory with GOPATH set -cdsudo() { - DIR="$1" - shift - CMD="cd $DIR && $@" - sudo --preserve-env=GOPATH --non-interactive bash -c "$CMD" +setup_rootless() { + req_env_var " + ROOTLESS_USER $ROOTLESS_USER + GOSRC $GOSRC + ENVLIB $ENVLIB + " + + if passwd --status $ROOTLESS_USER + then + echo "Updating $ROOTLESS_USER user permissions on possibly changed libpod code" + chown -R $ROOTLESS_USER:$ROOTLESS_USER "$GOSRC" + return 0 + fi + + # Guarantee independence from specific values + ROOTLESS_UID=$[RANDOM+1000] + ROOTLESS_GID=$[RANDOM+1000] + echo "creating $ROOTLESS_UID:$ROOTLESS_GID $ROOTLESS_USER user" + groupadd -g $ROOTLESS_GID $ROOTLESS_USER + useradd -g $ROOTLESS_GID -u $ROOTLESS_UID --no-user-group --create-home $ROOTLESS_USER + chown -R $ROOTLESS_USER:$ROOTLESS_USER "$GOSRC" + + echo "creating ssh keypair for $USER" + ssh-keygen -P "" -f $HOME/.ssh/id_rsa + + echo "Allowing ssh key for $ROOTLESS_USER" + (umask 077 && mkdir "/home/$ROOTLESS_USER/.ssh") + chown -R $ROOTLESS_USER:$ROOTLESS_USER "/home/$ROOTLESS_USER/.ssh" + install -o $ROOTLESS_USER -g $ROOTLESS_USER -m 0600 \ + "$HOME/.ssh/id_rsa.pub" "/home/$ROOTLESS_USER/.ssh/authorized_keys" + # Makes debugging easier + cat /root/.ssh/authorized_keys >> "/home/$ROOTLESS_USER/.ssh/authorized_keys" + + echo "Configuring subuid and subgid" + grep -q "${ROOTLESS_USER}" /etc/subuid || \ + echo "${ROOTLESS_USER}:$[ROOTLESS_UID * 100]:65536" | \ + tee -a /etc/subuid >> /etc/subgid + + echo "Setting permissions on automation files" + chmod 666 "$TIMESTAMPS_FILEPATH" + + echo "Copying $HOME/$ENVLIB" + install -o $ROOTLESS_USER -g $ROOTLESS_USER -m 0700 \ + "$HOME/$ENVLIB" "/home/$ROOTLESS_USER/$ENVLIB" + + echo "Configuring user's go environment variables" + su --login --command 'go env' $ROOTLESS_USER | \ + while read envline + do + X=$(echo "export $envline" | tee -a "/home/$ROOTLESS_USER/$ENVLIB") && echo "$X" + done } # Helper/wrapper script to only show stderr/stdout on non-zero exit @@ -300,21 +364,6 @@ EOF fi } -# Runs in testing VM, not image building -install_testing_dependencies() { - echo "Installing ginkgo, gomega, and easyjson into \$GOPATH=$GOPATH" - req_env_var " - GOPATH $GOPATH - GOSRC $GOSRC - " - cd "$GOSRC" - ooe.sh go get -u github.com/onsi/ginkgo/ginkgo - ooe.sh install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/ - ooe.sh go get github.com/onsi/gomega/... - ooe.sh go get -u github.com/mailru/easyjson/... - sudo install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/ -} - install_packer_copied_files(){ # Install cni config, policy and registry config sudo install -D -m 755 /tmp/libpod/cni/87-podman-bridge.conflist \ diff --git a/contrib/cirrus/notice_master_failure.sh b/contrib/cirrus/notice_master_failure.sh new file mode 100755 index 000000000..4b09331d3 --- /dev/null +++ b/contrib/cirrus/notice_master_failure.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +source $(dirname $0)/lib.sh + +# mIRC "escape" codes are the most standard, for a non-standard client-side interpretation. +ETX="$(echo -n -e '\x03')" +RED="${ETX}4" +NOR="$(echo -n -e '\x0f')" + +if [[ "$CIRRUS_BRANCH" =~ "master" ]] +then + BURL="https://cirrus-ci.com/build/$CIRRUS_BUILD_ID" + ircmsg "${RED}[Action Recommended]: ${NOR}Post-merge testing ${RED}$CIRRUS_BRANCH failed${NOR} in $CIRRUS_TASK_NAME on $(os_release_id)-$(os_release_ver): $BURL. Please investigate, and re-run if appropriate." +fi + +# This script assumed to be executed on failure +die 1 "Testing Failed" diff --git a/contrib/cirrus/packer/centos_setup.sh b/contrib/cirrus/packer/centos_setup.sh index 923f2563b..d947a1d7f 100644 --- a/contrib/cirrus/packer/centos_setup.sh +++ b/contrib/cirrus/packer/centos_setup.sh @@ -27,6 +27,7 @@ ooe.sh sudo yum -y install centos-release-scl epel-release ooe.sh sudo yum -y install \ PyYAML \ atomic-registries \ + bats \ btrfs-progs-devel \ bzip2 \ device-mapper-devel \ diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index de7ad4506..84aee7667 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -26,6 +26,7 @@ ooe.sh sudo dnf update -y ooe.sh sudo dnf install -y \ atomic-registries \ + bats \ btrfs-progs-devel \ bzip2 \ device-mapper-devel \ diff --git a/contrib/cirrus/packer/rhel_setup.sh b/contrib/cirrus/packer/rhel_setup.sh index ac6866a57..20be97f9b 100644 --- a/contrib/cirrus/packer/rhel_setup.sh +++ b/contrib/cirrus/packer/rhel_setup.sh @@ -33,6 +33,7 @@ ooe.sh sudo yum -y update ooe.sh sudo yum -y install \ PyYAML \ atomic-registries \ + bats \ btrfs-progs-devel \ bzip2 \ device-mapper-devel \ diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 5b7e1d714..24f1cce21 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -38,6 +38,7 @@ ooe.sh sudo -E apt-get -qq install \ apparmor \ autoconf \ automake \ + bats \ bison \ btrfs-tools \ build-essential \ diff --git a/contrib/cirrus/rootless_test.sh b/contrib/cirrus/rootless_test.sh new file mode 100755 index 000000000..d0e2ceb95 --- /dev/null +++ b/contrib/cirrus/rootless_test.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +set -e +source $HOME/.bash_profile + +cd $GOSRC +source $(dirname $0)/lib.sh + +req_env_var " +GOSRC $GOSRC +OS_RELEASE_ID $OS_RELEASE_ID +OS_RELEASE_VER $OS_RELEASE_VER +" + +if ! run_rootless +then + echo "Error: Expected rootless env. vars not set or empty" + exit 1 +fi + +echo "." +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 + +record_timestamp "rootless test end" diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh index 77c20d9bd..96d0e1b55 100755 --- a/contrib/cirrus/setup_environment.sh +++ b/contrib/cirrus/setup_environment.sh @@ -43,6 +43,7 @@ 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"' \ @@ -70,18 +71,20 @@ then *) bad_os_id_ver ;; esac - # Do the same for golang env. vars - go env | while read envline - do - X=$(echo "export $envline" | tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X" - done - cd "${GOSRC}/" + # Reload to incorporate any changes from above source "$SCRIPT_BASE/lib.sh" - # Only testing-VMs need deps installed, not image-builder VM - echo "$CIRRUS_TASK_NAME" | grep -q 'image' || \ - install_testing_dependencies # must exist in $GOPATH + 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 fi +show_env_vars + record_timestamp "env. setup end" diff --git a/contrib/cirrus/test/test_dot_cirrus_yaml.py b/contrib/cirrus/test/test_dot_cirrus_yaml.py new file mode 100755 index 000000000..2894bc45e --- /dev/null +++ b/contrib/cirrus/test/test_dot_cirrus_yaml.py @@ -0,0 +1,78 @@ +#!/bin/env python3 + +import sys +import os +import os.path +import unittest +import warnings +import yaml + +class TestCaseBase(unittest.TestCase): + + SCRIPT_PATH = os.path.realpath((os.path.dirname(sys.argv[0]))) + CIRRUS_WORKING_DIR = os.environ.get('CIRRUS_WORKING_DIR', + '{0}/../../../'.format(SCRIPT_PATH)) + + def setUp(self): + os.chdir(self.CIRRUS_WORKING_DIR) + + +class TestCirrusYAML(TestCaseBase): + + IMAGE_NAME_SUFFIX = '_CACHE_IMAGE_NAME' + ACTIVE_IMAGES_NAME = 'ACTIVE_CACHE_IMAGE_NAMES' + + def setUp(self): + TestCirrusYAML._cirrus = None + super().setUp() + + @property + def cirrus(self): + if TestCirrusYAML._cirrus is None: + with warnings.catch_warnings(): + warnings.filterwarnings("ignore",category=DeprecationWarning) + with open('.cirrus.yml', "r") as dot_cirrus_dot_yaml: + TestCirrusYAML._cirrus = yaml.load(dot_cirrus_dot_yaml) + return TestCirrusYAML._cirrus + + def _assert_get_cache_image_names(self, env): + inames = set([key for key in env.keys() + if key.endswith(self.IMAGE_NAME_SUFFIX)]) + self.assertNotEqual(inames, set()) + + ivalues = set([value for key, value in env.items() + if key in inames]) + self.assertNotEqual(ivalues, set()) + return ivalues + + def _assert_get_subdct(self, key, dct): + self.assertIn(key, dct) + return dct[key] + + def test_parse_yaml(self): + self.assertIsInstance(self.cirrus, dict) + + def test_active_cache_image_names(self): + env = self._assert_get_subdct('env', self.cirrus) + acin = self._assert_get_subdct(self.ACTIVE_IMAGES_NAME, env) + + for ivalue in self._assert_get_cache_image_names(env): + self.assertIn(ivalue, acin, + "The '{}' sub-key of 'env' should contain this among" + " its space-separated values." + "".format(self.ACTIVE_IMAGES_NAME)) + + + def test_cache_image_names_active(self): + env = self._assert_get_subdct('env', self.cirrus) + ivalues = self._assert_get_cache_image_names(env) + + for avalue in set(self._assert_get_subdct(self.ACTIVE_IMAGES_NAME, env).split()): + self.assertIn(avalue, ivalues, + "All space-separated values in the '{}' sub-key" + " of 'env' must also be used in a key with a '{}' suffix." + "".format(self.ACTIVE_IMAGES_NAME, self.IMAGE_NAME_SUFFIX)) + + +if __name__ == '__main__': + unittest.main(failfast=True, catchbreak=True, verbosity=0) diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile index 4d88ae9a6..e44c2fd4f 100644 --- a/contrib/gate/Dockerfile +++ b/contrib/gate/Dockerfile @@ -29,8 +29,9 @@ RUN dnf -y install \ python3-dateutil \ python3-psutil \ python3-pytoml \ + python3-pyyaml \ python3-varlink \ - skopeo-containers \ + containers-common \ slirp4netns \ rsync \ which \ diff --git a/contrib/gate/entrypoint.sh b/contrib/gate/entrypoint.sh index e16094cc0..0189cf7c5 100755 --- a/contrib/gate/entrypoint.sh +++ b/contrib/gate/entrypoint.sh @@ -10,6 +10,6 @@ # Working from a copy avoids needing to perturb the actual source files mkdir -p "$GOSRC" /usr/bin/rsync --recursive --links --quiet --safe-links \ - --perms --times "${SRCPATH}/" "${GOSRC}/" + --perms --times --delete "${SRCPATH}/" "${GOSRC}/" cd "$GOSRC" make "$@" diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 703b942b6..3324ee8f9 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -472,6 +472,7 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %{_mandir}/man1/*.1* %{_mandir}/man5/*.5* %{_datadir}/bash-completion/completions/* +%{_datadir}/zsh/site-functions/* %{_libexecdir}/%{name}/conmon %config(noreplace) %{_sysconfdir}/cni/net.d/87-%{name}-bridge.conflist %{_datadir}/containers/%{repo}.conf diff --git a/crio-umount.conf b/crio-umount.conf deleted file mode 100644 index 5177e6362..000000000 --- a/crio-umount.conf +++ /dev/null @@ -1,8 +0,0 @@ -# This contains a list of paths on host which will be unmounted inside -# container. (If they are mounted inside container). - -# If there is a "/*" at the end, that means only mounts underneath that -# mounts (submounts) will be unmounted but top level mount will remain -# in place. -/var/run/containers/* -/var/lib/containers/storage/* diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index 9a19e1224..777edeacb 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -91,6 +91,10 @@ libpod to manage containers. Directory where named volumes will be created in using the default volume driver. By default this will be configured relative to where containers/storage stores containers. +**network_cmd_path**="" + Path to the command binary to use for setting up a network. It is currently only used for setting up + a slirp4netns network. If "" is used then the binary is looked up using the $PATH environment variable. + ## FILES `/usr/share/containers/libpod.conf`, default libpod configuration path diff --git a/docs/podman-attach.1.md b/docs/podman-attach.1.md index ceb945a0e..11cecc16c 100644 --- a/docs/podman-attach.1.md +++ b/docs/podman-attach.1.md @@ -11,13 +11,12 @@ The attach command allows you to attach to a running container using the contain or name, either to view its ongoing output or to control it interactively. You can detach from the container (and leave it running) using a configurable key sequence. The default -sequence is CTRL-p CTRL-q. You configure the key sequence using the --detach-keys option +sequence is `ctrl-p,ctrl-q`. You configure the key sequence using the --detach-keys option ## OPTIONS -**--detach-keys** +**--detach-keys**="" -Override the key sequence for detaching a container. Format is a single character [a-Z] or -ctrl-[value] where [value] is one of: a-z, @, ^, [, , or _. +Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`. **--latest, -l** diff --git a/docs/podman-build.1.md b/docs/podman-build.1.md index fdae48b93..ccc8bd900 100644 --- a/docs/podman-build.1.md +++ b/docs/podman-build.1.md @@ -1,7 +1,7 @@ % podman-build(1) ## NAME -podman\-build - Build a container image using a Dockerfile. +podman\-build - Build a container image using a Dockerfile ## SYNOPSIS **podman build** [*options*] *context* @@ -209,7 +209,7 @@ Write the image ID to the file. Sets the configuration for IPC namespaces when handling `RUN` instructions. The configured value can be "" (the empty string) or "container" to indicate that a new IPC namespace should be created, or it can be "host" to indicate -that the IPC namespace in which `buildah` itself is being run should be reused, +that the IPC namespace in which `podman` itself is being run should be reused, or it can be the path to an IPC namespace which is already in use by another process. @@ -269,7 +269,7 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. Sets the configuration for network namespaces when handling `RUN` instructions. The configured value can be "" (the empty string) or "container" to indicate that a new network namespace should be created, or it can be "host" to indicate -that the network namespace in which `buildah` itself is being run should be +that the network namespace in which `podman` itself is being run should be reused, or it can be the path to a network namespace which is already in use by another process. @@ -282,19 +282,27 @@ Do not use existing cached images for the container build. Build from the start Sets the configuration for PID namespaces when handling `RUN` instructions. The configured value can be "" (the empty string) or "container" to indicate that a new PID namespace should be created, or it can be "host" to indicate -that the PID namespace in which `buildah` itself is being run should be reused, +that the PID namespace in which `podman` itself is being run should be reused, or it can be the path to a PID namespace which is already in use by another process. **--pull** -Pull the image if it is not present. If this flag is disabled (with -*--pull=false*) and the image is not present, the image will not be pulled. +When the flag is enabled, attempt to pull the latest image from the registries +listed in registries.conf if a local image does not exist or the image is newer +than the one in storage. Raise an error if the image is not in any listed +registry and is not present locally. + +If the flag is disabled (with *--pull=false*), do not pull the image from the +registry, use only the local version. Raise an error if the image is not +present locally. + Defaults to *true*. **--pull-always** -Pull the image even if a version of the image is already present. +Pull the image from the first registry it is found in as listed in registries.conf. +Raise an error if not found in the registries, even if the image is present locally. **--quiet, -q** @@ -398,7 +406,7 @@ include: Sets the configuration for user namespaces when handling `RUN` instructions. The configured value can be "" (the empty string) or "container" to indicate that a new user namespace should be created, it can be "host" to indicate that -the user namespace in which `buildah` itself is being run should be reused, or +the user namespace in which `podman` itself is being run should be reused, or it can be the path to an user namespace which is already in use by another process. @@ -452,7 +460,7 @@ in the `/etc/subuid` file which correspond to the specified user. Commands run when handling `RUN` instructions will default to being run in their own user namespaces, configured using the UID and GID maps. If --userns-gid-map-group is specified, but --userns-uid-map-user is not -specified, `buildah` will assume that the specified group name is also a +specified, `podman` will assume that the specified group name is also a suitable user name to use as the default setting for this option. **--userns-gid-map-group** *group* @@ -463,7 +471,7 @@ in the `/etc/subgid` file which correspond to the specified group. Commands run when handling `RUN` instructions will default to being run in their own user namespaces, configured using the UID and GID maps. If --userns-uid-map-user is specified, but --userns-gid-map-group is not -specified, `buildah` will assume that the specified user name is also a +specified, `podman` will assume that the specified user name is also a suitable group name to use as the default setting for this option. **--uts** *how* @@ -471,7 +479,7 @@ suitable group name to use as the default setting for this option. Sets the configuration for UTS namespaces when the handling `RUN` instructions. The configured value can be "" (the empty string) or "container" to indicate that a new UTS namespace should be created, or it can be "host" to indicate -that the UTS namespace in which `buildah` itself is being run should be reused, +that the UTS namespace in which `podman` itself is being run should be reused, or it can be the path to a UTS namespace which is already in use by another process. diff --git a/docs/podman-container-exists.1.md b/docs/podman-container-exists.1.md index 76701e2c2..8feb736f8 100644 --- a/docs/podman-container-exists.1.md +++ b/docs/podman-container-exists.1.md @@ -2,12 +2,10 @@ % Brent Baude % November 2018 # NAME -podman-container-exists- Check if a container exists in local storage +podman-container-exists - Check if a container exists in local storage # SYNOPSIS -**podman container exists** -[**-h**|**--help**] -CONTAINER +**podman container exists** [*-h*|*--help*] *container* # DESCRIPTION **podman container exists** checks if a container exists in local storage. The **ID** or **Name** diff --git a/docs/podman-container-prune.1.md b/docs/podman-container-prune.1.md index 1f3ef1d41..194dd3dae 100644 --- a/docs/podman-container-prune.1.md +++ b/docs/podman-container-prune.1.md @@ -5,8 +5,7 @@ podman-container-prune - Remove all stopped containers # SYNOPSIS -**podman container prune** -[**-h**|**--help**] +**podman container prune** [*-h*|*--help*] # DESCRIPTION **podman container prune** removes all stopped containers from local storage. diff --git a/docs/podman-container-runlabel.1.md b/docs/podman-container-runlabel.1.md index c5d7a278f..7fa9805e6 100644 --- a/docs/podman-container-runlabel.1.md +++ b/docs/podman-container-runlabel.1.md @@ -9,10 +9,10 @@ podman-container-runlabel - Execute Image Label Method [**-h**|**--help**] [**--display**] [**-n**][**--name**[=*NAME*]] -[**-p**][[**--pull**]] [**--rootfs**=*ROOTFS*] [**--set**=*NAME*=*VALUE*] [**--storage**] +[**--replace**] LABEL IMAGE [ARG...] # DESCRIPTION @@ -27,7 +27,7 @@ If the container image has a LABEL INSTALL instruction like the following: `podman container runlabel` will set the following environment variables for use in the command: If the container image does not have the desired label, an error message will be displayed along with a non-zero -return code. +return code. If the image is not found in local storage, Podman will attempt to pull it first. Note: Podman will always ensure that `podman` is the first argument of the command being executed. @@ -82,13 +82,15 @@ 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. -**p** **--pull** - Pull the image if it cannot be found in local storage. - **--quiet, -q** Suppress output information when pulling images +**--replace** + +If a container exists of the default or given name, as needed it will be stopped, deleted and a new container will be +created from this image. + **--signature-policy="PATHNAME"** Pathname of a signature policy file to use. It is not recommended that this diff --git a/docs/podman-cp.1.md b/docs/podman-cp.1.md index 37426b236..44612003d 100644 --- a/docs/podman-cp.1.md +++ b/docs/podman-cp.1.md @@ -4,12 +4,12 @@ podman\-cp - Copy files/folders between a container and the local filesystem ## SYNOPSIS -**podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH** +**podman cp** [*container*:]*src_path* [*container*:]*dest_path* ## DESCRIPTION -Copies the contents of **SRC_PATH** to the **DEST_PATH**. You can copy from the containers's filesystem to the local machine or the reverse, from the local filesystem to the container. +Copies the contents of **src_path** to the **dest_path**. You can copy from the containers's filesystem to the local machine or the reverse, from the local filesystem to the container. -The CONTAINER can be a running or stopped container. The **SRC_PATH** or **DEST_PATH** can be a file or directory. +The CONTAINER can be a running or stopped container. The **src_path** or **dest_path** can be a file or directory. The **podman cp** command assumes container paths are relative to the container's / (root) directory. @@ -20,40 +20,45 @@ The command sees **compassionate_darwin:/tmp/foo/myfile.txt** and **compassionat Local machine paths can be an absolute or relative value. The command interprets a local machine's relative paths as relative to the current working directory where **podman cp** is run. -Assuming a path separator of /, a first argument of **SRC_PATH** and second argument of **DEST_PATH**, the behavior is as follows: +Assuming a path separator of /, a first argument of **src_path** and second argument of **dest_path**, the behavior is as follows: -**SRC_PATH** specifies a file - - **DEST_PATH** does not exist - - the file is saved to a file created at **DEST_PATH** - - **DEST_PATH** does not exist and ends with / - - **DEST_PATH** is created as a directory and the file is copied into this directory using the basename from **SRC_PATH** - - **DEST_PATH** exists and is a file +**src_path** specifies a file + - **dest_path** does not exist + - the file is saved to a file created at **dest_path** + - **dest_path** does not exist and ends with / + - **dest_path** is created as a directory and the file is copied into this directory using the basename from **src_path** + - **dest_path** exists and is a file - the destination is overwritten with the source file's contents - - **DEST_PATH** exists and is a directory - - the file is copied into this directory using the basename from **SRC_PATH** + - **dest_path** exists and is a directory + - the file is copied into this directory using the basename from **src_path** -**SRC_PATH** specifies a directory - - **DEST_PATH** does not exist - - **DEST_PATH** is created as a directory and the contents of the source directory are copied into this directory - - **DEST_PATH** exists and is a file +**src_path** specifies a directory + - **dest_path** does not exist + - **dest_path** is created as a directory and the contents of the source directory are copied into this directory + - **dest_path** exists and is a file - Error condition: cannot copy a directory to a file - - **DEST_PATH** exists and is a directory - - **SRC_PATH** ends with / + - **dest_path** exists and is a directory + - **src_path** ends with / - the source directory is copied into this directory - - **SRC_PATH** ends with /. (that is: slash followed by dot) + - **src_path** ends with /. (that is: slash followed by dot) - the content of the source directory is copied into this directory -The command requires **SRC_PATH** and **DEST_PATH** to exist according to the above rules. +The command requires **src_path** and **dest_path** to exist according to the above rules. -If **SRC_PATH** is local and is a symbolic link, the symbolic target, is copied by default. +If **src_path** is local and is a symbolic link, the symbolic target, is copied by default. A colon (:) is used as a delimiter between CONTAINER and its path. -You can also use : when specifying paths to a **SRC_PATH** or **DEST_PATH** on a local machine, for example, `file:name.txt`. +You can also use : when specifying paths to a **src_path** or **dest_path** on a local machine, for example, `file:name.txt`. If you use a : in a local machine path, you must be explicit with a relative or absolute path, for example: `/path/to/file:name.txt` or `./file:name.txt` +## OPTIONS + +**--extract** + +Extract the tar file into the destination directory. If the destination directory is not provided, extract the tar file into the root directory. ## ALTERNATIVES @@ -100,5 +105,7 @@ podman cp containerID:/myapp/ /myapp/ podman cp containerID:/home/myuser/. /home/myuser/ +podman cp --extract /home/myuser/myfiles.tar.gz containerID:/myfiles + ## SEE ALSO podman(1), podman-mount(1), podman-umount(1) diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 342ef59c3..2e176db76 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -164,17 +164,16 @@ Detached mode: run the container in the background and print the new container I At any time you can run **podman ps** in the other shell to view a list of the running containers. You can reattach to a -detached container with **podman attach**. If you choose to run a container in -the detached mode, then you cannot use the **-rm** option. +detached container with **podman attach**. When attached in the tty mode, you can detach from the container (and leave it -running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`. +running) using a configurable key sequence. The default sequence is `ctrl-p,ctrl-q`. You configure the key sequence using the **--detach-keys** option or a configuration file. See **config-json(5)** for documentation on using a configuration file. **--detach-keys**="" -Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`. +Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`. **--device**=[] @@ -257,6 +256,23 @@ The following example maps uids 0-2000 in the container to the uids 30000-31999 Add additional groups to run as +**--healthchech**="" + +Set or alter a healthcheck for a container. The value must be of the format of: + + `[OPTIONS] CMD command` + + where options can be any of the follow: + * --interval=DURATION (default: 30s) + * --timeout=DURATION (default: 30s) + * --start-period=DURATION (default: 0s) + * --retries=N (default: 3) + +Note: options are *not* required. + +The command is a command to be executed inside your container that determines your container health. The +command is required. + **--hostname**="" Container host name diff --git a/docs/podman-events.1.md b/docs/podman-events.1.md new file mode 100644 index 000000000..3463d2aaa --- /dev/null +++ b/docs/podman-events.1.md @@ -0,0 +1,139 @@ +% podman-events(1) + +## NAME +podman\-events - Monitor Podman events + +## SYNOPSIS +**podman events** [*options*] + +## DESCRIPTION + +Monitor and print events that occur in Podman. Each event will include a timestamp, +a type, a status, name (if applicable), and image (if applicable). + +The *container* event type will report the follow statuses: + * attach + * checkpoint + * cleanup + * commit + * create + * exec + * export + * import + * init + * kill + * mount + * pause + * prune + * remove + * restore + * start + * stop + * sync + * unmount + * unpause + * wait + +The *pod* event type will report the follow statuses: + * create + * kill + * pause + * remove + * start + * stop + * unpause + +The *image* event type will report the following statuses: + * prune + * pull + * push + * remove + * save + * tag + * untag + +The *volume* type will report the following statuses: + * create + * prune + * remove + + +## OPTIONS + +**--help** + +Print usage statement. + +**--format** + +Format the output using the given Go template. An output value of *json* is not supported. + + +**--filter**=[] + +Filter events that are displayed. They must be in the format of "filter=value". The following +filters are supported: + * container=name_or_id + * event=event_status (described above) + * image=name_or_id + * pod=name_or_id + * volume=name_or_id + * type=event_type (described above) + +In the case where an ID is used, the ID may be in its full or shortened form. + +**--since**=[] + +Show all events created since the given timestamp + + +**--until**=[] + +Show all events created until the given timestamp + +The *since* and *until* values can be RFC3339Nano time stamps or a Go duration string such as 10m, 5h. If no +*since* or *until* values are provided, only new events will be shown. + +## EXAMPLES + +Showing podman events +``` +$ podman events +2019-03-02 10:33:42.312377447 -0600 CST container create 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen) +2019-03-02 10:33:46.958768077 -0600 CST container init 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen) +2019-03-02 10:33:46.973661968 -0600 CST container start 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen) +2019-03-02 10:33:50.833761479 -0600 CST container stop 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen) +2019-03-02 10:33:51.047104966 -0600 CST container cleanup 34503c192940 (image=docker.io/library/alpine:latest, name=friendly_allen) +``` + +Show only podman create events +``` +$ podman events --filter event=create +2019-03-02 10:36:01.375685062 -0600 CST container create 20dc581f6fbf (image=docker.io/library/alpine:latest, name=sharp_morse) +2019-03-02 10:36:08.561188337 -0600 CST container create 58e7e002344c (image=k8s.gcr.io/pause:3.1, name=3e701f270d54-infra) +2019-03-02 10:36:13.146899437 -0600 CST volume create cad6dc50e087 (image=, name=cad6dc50e0879568e7d656bd004bd343d6035e7fc4024e1711506fe2fd459e6f) +2019-03-02 10:36:29.978806894 -0600 CST container create d81e30f1310f (image=docker.io/library/busybox:latest, name=musing_newton) +``` + +Show only podman pod create events +``` +$ podman events --filter event=create --filter type=pod +2019-03-02 10:44:29.601746633 -0600 CST pod create 1df5ebca7b44 (image=, name=confident_hawking) +2019-03-02 10:44:42.374637304 -0600 CST pod create ca731231718e (image=, name=webapp) +2019-03-02 10:44:47.486759133 -0600 CST pod create 71e807fc3a8e (image=, name=reverent_swanson) +``` + +Show only podman events created in the last five minutes: +``` +$ sudo podman events --since 5m +2019-03-02 10:44:29.598835409 -0600 CST container create b629d10d3831 (image=k8s.gcr.io/pause:3.1, name=1df5ebca7b44-infra) +2019-03-02 10:44:29.601746633 -0600 CST pod create 1df5ebca7b44 (image=, name=confident_hawking) +2019-03-02 10:44:42.371100253 -0600 CST container create 170a0f457d00 (image=k8s.gcr.io/pause:3.1, name=ca731231718e-infra) +2019-03-02 10:44:42.374637304 -0600 CST pod create ca731231718e (image=, name=webapp) +``` + +## SEE ALSO +podman(1) + +## HISTORY +March 2019, Originally compiled by Brent Baude <bbaude@redhat.com> diff --git a/docs/podman-exec.1.md b/docs/podman-exec.1.md index 14088b468..b74713b0b 100644 --- a/docs/podman-exec.1.md +++ b/docs/podman-exec.1.md @@ -26,6 +26,10 @@ to run containers such as CRI-O, the last started container could be from eithe The latest option is not supported on the remote client. +**--preserve-fds=N** + +Pass down to the process N additional file descriptors (in addition to 0, 1, 2). The total FDs will be 3+N. + **--privileged** Give the process extended Linux capabilities when running the command in container. diff --git a/docs/podman-export.1.md b/docs/podman-export.1.md index a8810f534..d0e365056 100644 --- a/docs/podman-export.1.md +++ b/docs/podman-export.1.md @@ -1,7 +1,7 @@ % podman-export(1) ## NAME -podman\-export - Export container's filesystem contents as a tar archive +podman\-export - Export a container's filesystem contents as a tar archive ## SYNOPSIS **podman export** [*options*] *container* diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index d4bed8ab1..b6fa78606 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -5,10 +5,7 @@ podman-generate-kube - Generate Kubernetes YAML # SYNOPSIS -**podman generate kube ** -[**-h**|**--help**] -[**-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 diff --git a/docs/podman-generate.1.md b/docs/podman-generate.1.md index 66afacd0b..d1736f38e 100644 --- a/docs/podman-generate.1.md +++ b/docs/podman-generate.1.md @@ -1,7 +1,7 @@ % podman-generate(1) ## NAME -podman\-container - generate structured data based for a containers and pods +podman\-generate - Generate structured data based for a containers and pods ## SYNOPSIS **podman generate** *subcommand* diff --git a/docs/podman-healthcheck-run.1.md b/docs/podman-healthcheck-run.1.md new file mode 100644 index 000000000..e19c6250c --- /dev/null +++ b/docs/podman-healthcheck-run.1.md @@ -0,0 +1,39 @@ +% podman-healthcheck-run(1) + +## NAME +podman\-healthcheck\-run - Run a container healthcheck + +## SYNOPSIS +**podman healthcheck run** [*options*] *container* + +## DESCRIPTION + +Runs the healthcheck command defined in a running container manually. The resulting error codes are defined +as follows: + +* 0 = healthcheck command succeeded +* 1 = healthcheck command failed +* 125 = an error has occurred + +Possible errors that can occur during the healthcheck are: +* unable to find the container +* container has no defined healthcheck +* container is not running + +## OPTIONS +**--help** + +Print usage statement + + +## EXAMPLES + +``` +$ podman healtcheck run mywebapp +``` + +## SEE ALSO +podman-healthcheck(1) + +## HISTORY +Feb 2019, Originally compiled by Brent Baude <bbaude@redhat.com> diff --git a/docs/podman-healthcheck.1.md b/docs/podman-healthcheck.1.md new file mode 100644 index 000000000..91c3e4345 --- /dev/null +++ b/docs/podman-healthcheck.1.md @@ -0,0 +1,22 @@ +% podman-healthcheck(1) + +## NAME +podman\-healthcheck - Manage healthchecks for containers + +## SYNOPSIS +**podman healthcheck** *subcommand* + +## DESCRIPTION +podman healthcheck is a set of subcommands that manage container healthchecks + +## SUBCOMMANDS + +| Command | Man Page | Description | +| ------- | ------------------------------------------------- | ------------------------------------------------------------------------------ | +| run | [podman-healthcheck-run(1)](podman-healthcheck-run.1.md) | Run a container healthcheck | + +## SEE ALSO +podman(1) + +## HISTORY +Feb 2019, Originally compiled by Brent Baude <bbaude@redhat.com> diff --git a/docs/podman-history.1.md b/docs/podman-history.1.md index bca8cb1d2..8335428a8 100644 --- a/docs/podman-history.1.md +++ b/docs/podman-history.1.md @@ -1,7 +1,7 @@ % podman-history(1) ## NAME -podman\-history - Shows the history of an image +podman\-history - Show the history of an image ## SYNOPSIS **podman history** [*options*] *image*[:*tag*|@*digest*] diff --git a/docs/podman-image-exists.1.md b/docs/podman-image-exists.1.md index e04c23721..1dc264a6b 100644 --- a/docs/podman-image-exists.1.md +++ b/docs/podman-image-exists.1.md @@ -2,12 +2,10 @@ % Brent Baude % November 2018 # NAME -podman-image-exists- Check if an image exists in local storage +podman-image-exists - Check if an image exists in local storage # SYNOPSIS -**podman image exists** -[**-h**|**--help**] -IMAGE +**podman image exists** [*-h*|*--help*] *image* # DESCRIPTION **podman image exists** checks if an image exists in local storage. The **ID** or **Name** diff --git a/docs/podman-image-prune.1.md b/docs/podman-image-prune.1.md index df912c380..7d5fd2fc8 100644 --- a/docs/podman-image-prune.1.md +++ b/docs/podman-image-prune.1.md @@ -5,9 +5,7 @@ podman-image-prune - Remove all unused images # SYNOPSIS -**podman image prune** -[**-a**|**--all**] -[**-h**|**--help**] +**podman image prune** [*-a*|*--all*] [*-h*|*--help*] # DESCRIPTION **podman image prune** removes all dangling images from local storage. With the `all` option, diff --git a/docs/podman-image-sign.1.md b/docs/podman-image-sign.1.md index 232bc87fe..804ee03db 100644 --- a/docs/podman-image-sign.1.md +++ b/docs/podman-image-sign.1.md @@ -1,7 +1,7 @@ % podman-image-sign(1) # NAME -podman-image-sign- Create a signature for an image +podman-image-sign - Create a signature for an image # SYNOPSIS **podman image sign** diff --git a/docs/podman-image-tree.1.md b/docs/podman-image-tree.1.md new file mode 100644 index 000000000..acd5ffcbf --- /dev/null +++ b/docs/podman-image-tree.1.md @@ -0,0 +1,88 @@ +% podman-image-tree(1) + +## NAME +podman\-image\-tree - Prints layer hierarchy of an image in a tree format + +## SYNOPSIS +**podman image tree** [*image*:*tag*]|[*image-id*] +[**--help**|**-h**] + +## DESCRIPTION +Prints layer hierarchy of an image in a tree format. +If you do not provide a *tag*, podman will default to `latest` for the *image*. +Layers are indicated with image tags as `Top Layer of`, when the tag is known locally. +## OPTIONS + +**--help**, **-h** + +Print usage statement + +**--whatrequires** + +Show all child images and layers of the specified image + +## EXAMPLES + +``` +$ podman pull docker.io/library/wordpress +$ podman pull docker.io/library/php:7.2-apache + +$ podman image tree docker.io/library/wordpress +Image ID: 6e880d17852f +Tags: [docker.io/library/wordpress:latest] +Size: 429.9MB +Image Layers +├── ID: 3c816b4ead84 Size: 58.47MB +├── ID: e39dad2af72e Size: 3.584kB +├── ID: b2d6a702383c Size: 213.6MB +├── ID: 94609408badd Size: 3.584kB +├── ID: f4dddbf86725 Size: 43.04MB +├── ID: 8f695df43a4c Size: 11.78kB +├── ID: c29d67bf8461 Size: 9.728kB +├── ID: 23f4315918f8 Size: 7.68kB +├── ID: d082f93a18b3 Size: 13.51MB +├── ID: 7ea8bedcac69 Size: 4.096kB +├── ID: dc3bbf7b3dc0 Size: 57.53MB +├── ID: fdbbc6404531 Size: 11.78kB +├── ID: 8d24785437c6 Size: 4.608kB +├── ID: 80715f9e8880 Size: 4.608kB Top Layer of: [docker.io/library/php:7.2-apache] +├── ID: c93cbcd6437e Size: 3.573MB +├── ID: dece674f3cd1 Size: 4.608kB +├── ID: 834f4497afda Size: 7.168kB +├── ID: bfe2ce1263f8 Size: 40.06MB +└── ID: 748e99b214cf Size: 11.78kB Top Layer of: [docker.io/library/wordpress:latest] + +$ podman pull docker.io/circleci/ruby:latest +$ podman pull docker.io/library/ruby:latest + +$ podman image tree ae96a4ad4f3f --whatrequires +Image ID: ae96a4ad4f3f +Tags: [docker.io/library/ruby:latest] +Size: 894.2MB +Image Layers +└── ID: 9c92106221c7 Size: 2.56kB Top Layer of: [docker.io/library/ruby:latest] + ├── ID: 1b90f2b80ba0 Size: 3.584kB + │ ├── ID: 42b7d43ae61c Size: 169.5MB + │ ├── ID: 26dc8ba99ec3 Size: 2.048kB + │ ├── ID: b4f822db8d95 Size: 3.957MB + │ ├── ID: 044e9616ef8a Size: 164.7MB + │ ├── ID: bf94b940200d Size: 11.75MB + │ ├── ID: 4938e71bfb3b Size: 8.532MB + │ └── ID: f513034bf553 Size: 1.141MB + ├── ID: 1e55901c3ea9 Size: 3.584kB + ├── ID: b62835a63f51 Size: 169.5MB + ├── ID: 9f4e8857f3fd Size: 2.048kB + ├── ID: c3b392020e8f Size: 3.957MB + ├── ID: 880163026a0a Size: 164.8MB + ├── ID: 8c78b2b14643 Size: 11.75MB + ├── ID: 830370cfa182 Size: 8.532MB + └── ID: 567fd7b7bd38 Size: 1.141MB Top Layer of: [docker.io/circleci/ruby:latest] + +``` + + +## SEE ALSO +podman(1), crio(8) + +## HISTORY +Feb 2019, Originally compiled by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> diff --git a/docs/podman-image-trust.1.md b/docs/podman-image-trust.1.md index 819035040..f96a7996f 100644 --- a/docs/podman-image-trust.1.md +++ b/docs/podman-image-trust.1.md @@ -1,11 +1,11 @@ % podman-image-trust "1" # NAME -podman\-trust - Manage container registry image trust policy +podman\-image\-trust - Manage container registry image trust policy # SYNOPSIS -**podman image trust set|show** +**podman image trust** set|show [**-h**|**--help**] [**-j**|**--json**] [**--raw**] diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index b4ae752f6..01cf08d62 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -14,19 +14,20 @@ The image command allows you to manage images | Command | Man Page | Description | | -------- | ----------------------------------------------- | --------------------------------------------------------------------------- | | build | [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. | -| exists | [podman-image-exists(1)](podman-image-exists.1.md) | Check if a image exists in local storage. | +| exists | [podman-image-exists(1)](podman-image-exists.1.md) | Check if an image exists in local storage. | | history | [podman-history(1)](podman-history.1.md) | Show the history of an image. | | import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. | | list | [podman-images(1)](podman-images.1.md) | List the container images on the system.(alias ls) | | load | [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. | -| prune | [podman-image-prune(1)](podman-image-prune.1.md)| Removed all unused images from the local store. | +| prune | [podman-image-prune(1)](podman-image-prune.1.md)| Remove all unused images from the local store. | | pull | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. | | push | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. | | rm | [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. | | save | [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. | | sign | [podman-image-sign(1)](podman-image-sign.1.md) | Sign an image. | | tag | [podman-tag(1)](podman-tag.1.md) | Add an additional name to a local image. | +| tree | [podman-image-tree(1)](podman-image-tree.1.md) | Prints layer hierarchy of an image in a tree format. | | trust | [podman-image-trust(1)](podman-image-trust.1.md)| Manage container image trust policy. | ## SEE ALSO diff --git a/docs/podman-inspect.1.md b/docs/podman-inspect.1.md index 5748f29f4..712891ad6 100644 --- a/docs/podman-inspect.1.md +++ b/docs/podman-inspect.1.md @@ -6,6 +6,10 @@ podman\-inspect - Display a container or image's configuration ## SYNOPSIS **podman inspect** [*options*] *name* ... +**podman image inspect** [*options*] *image* + +**podman container inspect** [*options*] *container* + ## DESCRIPTION This displays the low-level information on containers and images identified by name or ID. By default, this will render all results in a JSON array. If the container and image have the same name, this will return container JSON for @@ -16,6 +20,7 @@ unspecified type. If a format is specified, the given template will be executed **--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*) **--format, -f="FORMAT"** @@ -27,7 +32,7 @@ The keys of the returned JSON can be used as the values for the --format flag (s Instead of providing the container name or ID, use the last created container. If you use methods other than Podman to run containers such as CRI-O, the last started container could be from either of those methods. -The latest option is not supported on the remote client. +The latest option is not supported on the remote client or when invoked as *podman image inspect*. **--size, -s** @@ -94,12 +99,12 @@ overlay ``` ``` -# podman inspect --format "size: {{.Size}}" alpine +# podman image inspect --format "size: {{.Size}}" alpine size: 4405240 ``` ``` -podman inspect --latest --format {{.EffectiveCaps}} +podman container inspect --latest --format {{.EffectiveCaps}} [CAP_CHOWN CAP_DAC_OVERRIDE CAP_FSETID CAP_FOWNER CAP_MKNOD CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETFCAP CAP_SETPCAP CAP_NET_BIND_SERVICE CAP_SYS_CHROOT CAP_KILL CAP_AUDIT_WRITE] ``` diff --git a/docs/podman-load.1.md b/docs/podman-load.1.md index 8b6501a5c..8a1660c63 100644 --- a/docs/podman-load.1.md +++ b/docs/podman-load.1.md @@ -1,22 +1,24 @@ % podman-load(1) ## NAME -podman\-load - Load an image from docker archive +podman\-load - Load an image from a container image archive into container storage ## SYNOPSIS -**podman load** *name*[:*tag*|@*digest*] +**podman load** [*name*[:*tag*]] ## DESCRIPTION -**podman load** copies an image from either **docker-archive** or **oci-archive** stored -on the local machine. **podman load** reads from stdin by default or a file if the **input** flag is set. -The **quiet** flag suppresses the output when set. +**podman load** loads an image from either an **oci-archive** or **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. +You can also specify a name for the image if the archive does not contain a named reference, of if you want an additonal name for the local image. + +The **quiet** option suppresses the progress output when set. Note: `:` is a restricted character and cannot be part of the file name. + **podman [GLOBAL OPTIONS]** **podman load [GLOBAL OPTIONS]** -**podman load [OPTIONS] NAME[:TAG|@DIGEST]** +**podman load [OPTIONS] NAME[:TAG]** ## OPTIONS @@ -28,7 +30,7 @@ The remote client requires the use of this option. **--quiet, -q** -Suppress the output +Suppress the progress output **--signature-policy="PATHNAME"** @@ -75,7 +77,7 @@ Loaded image: registry.fedoraproject.org/fedora:latest ``` ## SEE ALSO -podman(1), podman-save(1), crio(8) +podman(1), podman-save(1), podman-tag(1), crio(8) ## HISTORY July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> diff --git a/docs/podman-logs.1.md b/docs/podman-logs.1.md index bc02df954..ce5d890ce 100644 --- a/docs/podman-logs.1.md +++ b/docs/podman-logs.1.md @@ -1,13 +1,15 @@ -% podman-logs(1) +% podman-container-logs(1) ## NAME -podman\-logs - Fetch the logs of a container +podman\-container\-logs (podman\-logs) - Fetch the logs of one or more containers ## SYNOPSIS -**podman** **logs** [*options*] *container* +**podman** **container** **logs** [*options*] *container* [*container...*] + +**podman** **logs** [*options*] *container* [*container...*] ## DESCRIPTION -The podman logs command batch-retrieves whatever logs are present for a container at the time of execution. +The podman logs command batch-retrieves whatever logs are present for one or more containers at the time of execution. This does not guarantee execution order when combined with podman run (i.e. your run may not have generated any logs at the time you execute podman logs @@ -15,7 +17,11 @@ any logs at the time you execute podman logs **--follow, -f** -Follow log output. Default is false +Follow log output. Default is false. + +Note: If you are following a container which is removed `podman container rm` +or removed on exit `podman run --rm ...`, then there is a chance the the log +file will be removed before `podman logs` reads the final content. **--latest, -l** @@ -86,7 +92,7 @@ podman logs --since 10m myserver ``` ## SEE ALSO -podman(1) +podman(1), podman-run(1), podman-container-rm(1) ## HISTORY February 2018, Updated by Brent Baude <bbaude@redhat.com> diff --git a/docs/podman-play-kube.1.md b/docs/podman-play-kube.1.md index 2264f7a88..a9af961cd 100644 --- a/docs/podman-play-kube.1.md +++ b/docs/podman-play-kube.1.md @@ -5,7 +5,7 @@ podman-play-kube - Create pods and containers based on Kubernetes YAML # SYNOPSIS -**podman play kube ** +**podman play kube** [**-h**|**--help**] [**--authfile**] [**--cert-dir**] diff --git a/docs/podman-play.1.md b/docs/podman-play.1.md index 6d2a7beba..f0bf8ea41 100644 --- a/docs/podman-play.1.md +++ b/docs/podman-play.1.md @@ -1,7 +1,7 @@ % podman-play(1) ## NAME -podman\-container - play pods and containers based on a structured input file +podman\-play - Play pods and containers based on a structured input file ## SYNOPSIS **podman play** *subcommand* diff --git a/docs/podman-pod-create.1.md b/docs/podman-pod-create.1.md index 06f962849..d913083d1 100644 --- a/docs/podman-pod-create.1.md +++ b/docs/podman-pod-create.1.md @@ -81,6 +81,8 @@ $ podman pod create --name test $ podman pod create --infra=false $ podman pod create --infra-command /top + +$ podman pod create --publish 8443:443 ``` ## SEE ALSO diff --git a/docs/podman-pod-exists.1.md b/docs/podman-pod-exists.1.md index 8fb2fc90e..da3947511 100644 --- a/docs/podman-pod-exists.1.md +++ b/docs/podman-pod-exists.1.md @@ -2,12 +2,10 @@ % Brent Baude % December 2018 # NAME -podman-pod-exists- Check if a pod exists in local storage +podman-pod-exists - Check if a pod exists in local storage # SYNOPSIS -**podman pod exists** -[**-h**|**--help**] -POD +**podman pod exists** [*-h*|*--help*] *pod* # DESCRIPTION **podman pod exists** checks if a pod exists in local storage. The **ID** or **Name** diff --git a/docs/podman-pod-top.1.md b/docs/podman-pod-top.1.md index a77ca2b37..b235a70ad 100644 --- a/docs/podman-pod-top.1.md +++ b/docs/podman-pod-top.1.md @@ -4,7 +4,7 @@ podman\-pod\-top - Display the running processes of containers in a pod ## SYNOPSIS -**podman top** [*options*] *pod* [*format-descriptors*] +**podman pod top** [*options*] *pod* [*format-descriptors*] ## DESCRIPTION Display the running process of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. diff --git a/docs/podman-pod.1.md b/docs/podman-pod.1.md index 8f8403a40..1846d0411 100644 --- a/docs/podman-pod.1.md +++ b/docs/podman-pod.1.md @@ -19,7 +19,7 @@ podman pod is a set of subcommands that manage pods, or groups of containers. | kill | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. | | pause | [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | | ps | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. | -| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or mode pods. | +| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. | | rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. | | start | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. | | stats | [podman-pod-stats(1)](podman-pod-stats.1.md) | Display live stream resource usage stats for containers in one or more pods. | diff --git a/docs/podman-ps.1.md b/docs/podman-ps.1.md index b8b1c3d62..685a52bda 100644 --- a/docs/podman-ps.1.md +++ b/docs/podman-ps.1.md @@ -100,6 +100,7 @@ Valid filters are listed below: | before | [ID] or [Name] Containers created before this container | | since | [ID] or [Name] Containers created since this container | | volume | [VolumeName] or [MountpointDestination] Volume mounted in container | +| health | [Status] healthy or unhealthy | **--help**, **-h** @@ -148,6 +149,15 @@ CONTAINER ID IMAGE COMMAND CREATED STATUS 69ed779d8ef9f redis:alpine "redis-server" 25 hours ago Created 6379/tcp k8s_container1_podsandbox1_redhat.test.crio_redhat-test-crio_1 02f65160e14ca redis:alpine "redis-server" 19 hours ago Exited (-1) 19 hours ago 6379/tcp k8s_podsandbox1-redis_podsandbox1_redhat.test.crio_redhat-test-crio_0 ``` + +``` +$ podman ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +4089df24d4f3 docker.io/library/centos:latest /bin/bash 2 minutes ago Up 2 minutes ago 0.0.0.0:80->8080/tcp, 0.0.0.0:2000-2006->2000-2006/tcp manyports +92f58933c28c docker.io/library/centos:latest /bin/bash 3 minutes ago Up 3 minutes ago 192.168.99.100:1000-1006->1000-1006/tcp zen_sanderson + +``` + ## ps Print a list of containers diff --git a/docs/podman-push.1.md b/docs/podman-push.1.md index 3ce156010..bb17c7e03 100644 --- a/docs/podman-push.1.md +++ b/docs/podman-push.1.md @@ -4,7 +4,7 @@ podman\-push - Push an image from local storage to elsewhere ## SYNOPSIS -**podman push** [*options*] **image** [**destination**] +**podman push** [*options*] *image* [*destination*] ## DESCRIPTION Pushes an image from local storage to a specified destination. diff --git a/docs/podman-restart.1.md b/docs/podman-restart.1.md index dd28e34c7..a20eee243 100644 --- a/docs/podman-restart.1.md +++ b/docs/podman-restart.1.md @@ -1,10 +1,10 @@ % podman-restart(1) ## NAME -podman\-restart - Restart a container +podman\-restart - Restart one or more containers ## SYNOPSIS -**podman attach** [*options*] *container* ... +**podman restart** [*options*] *container* ... ## DESCRIPTION The restart command allows containers to be restarted using their ID or name. diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index bbf10a2ce..b8b3d51f0 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -170,11 +170,10 @@ Detached mode: run the container in the background and print the new container I At any time you can run **podman ps** in the other shell to view a list of the running containers. You can reattach to a -detached container with **podman attach**. If you choose to run a container in -the detached mode, then you cannot use the **-rm** option. +detached container with **podman attach**. When attached in the tty mode, you can detach from the container (and leave it -running) using a configurable key sequence. The default sequence is `CTRL-p CTRL-q`. +running) using a configurable key sequence. The default sequence is `ctrl-p,ctrl-q`. You configure the key sequence using the **--detach-keys** option or a configuration file. See **config-json(5)** for documentation on using a configuration file. @@ -265,6 +264,23 @@ The example maps gids 0-2000 in the container to the gids 30000-31999 on the hos Add additional groups to run as +**--healthchech**="" + +Set or alter a healthcheck for a container. The value must be of the format of: + + `[OPTIONS] CMD command` + + where options can be any of the follow: + * --interval=DURATION (default: 30s) + * --timeout=DURATION (default: 30s) + * --start-period=DURATION (default: 0s) + * --retries=N (default: 3) + +Note: options are *not* required. + +The command is a command to be executed inside your container that determines your container health. The +command is required. + **--hostname**="" Container host name @@ -873,7 +889,7 @@ During container image development, containers often need to write to the image content. Installing packages into /usr, for example. In production, applications seldom need to write to the image. Container applications write to volumes if they need to write to file systems at all. Applications can be -made more secure by running them in read-only mode using the - -read-only switch. +made more secure by running them in read-only mode using the --read-only switch. This protects the containers image from modification. Read only containers may still need to write temporary data. The best way to handle this is to mount tmpfs directories on /run and /tmp. @@ -1113,6 +1129,15 @@ KillMode=process WantedBy=multi-user.target ``` +### Configuring Storage Options from the command line + +Podman allows for the configuration of storage by changing the values +in the /etc/container/storage.conf or by using global options. This +shows how to setup and use fuse-overlayfs for a one time run of busybox +using global options. + +podman --log-level=debug --storage-driver overlay --storage-opt "overlay.mount_program=/usr/bin/fuse-overlayfs" run busybox /bin/sh + ### Rootless Containers Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils diff --git a/docs/podman-save.1.md b/docs/podman-save.1.md index a0b04d633..75aeda797 100644 --- a/docs/podman-save.1.md +++ b/docs/podman-save.1.md @@ -1,14 +1,13 @@ % podman-save(1) ## NAME -podman\-save - Save an image to docker-archive or oci-archive +podman\-save - Save an image to a container archive ## SYNOPSIS **podman save** [*options*] *name*[:*tag*] ## DESCRIPTION -**podman save** saves an image to either **docker-archive**, **oci-archive**, **oci-dir** (directory -with oci manifest type), or **docker-dir** (directory with v2s2 manifest type) on the local machine, +**podman save** saves an image to either **docker-archive**, **oci-archive**, **oci-dir** (directory with oci manifest type), or **docker-dir** (directory with v2s2 manifest type) on the local machine, default is **docker-archive**. **podman save** writes to STDOUT by default and can be redirected to a file using the **output** flag. The **quiet** flag suppresses the output when set. Note: `:` is a restricted character and cannot be part of the file name. diff --git a/docs/podman-start.1.md b/docs/podman-start.1.md index b0167003e..aa5362046 100644 --- a/docs/podman-start.1.md +++ b/docs/podman-start.1.md @@ -21,8 +21,7 @@ starting multiple containers. **--detach-keys** -Override the key sequence for detaching a container. Format is a single character [a-Z] or -ctrl-<value> where <value> is one of: a-z, @, ^, [, , or _. +Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`. **--interactive, -i** diff --git a/docs/podman-system-df.1.md b/docs/podman-system-df.1.md new file mode 100644 index 000000000..f33523dd6 --- /dev/null +++ b/docs/podman-system-df.1.md @@ -0,0 +1,57 @@ +% podman-system-df(1) podman + +## NAME +podman\-system\-df - Show podman disk usage + +## SYNOPSIS +**podman system df** [*options*] + +## DESCRIPTION +Show podman disk usage + +## OPTIONS +**--format**="" + +Pretty-print images using a Go template + +**-v, --verbose**[=false] +Show detailed information on space usage + +## EXAMPLE + +$ podman system df +TYPE TOTAL ACTIVE SIZE RECLAIMABLE +Images 6 2 281MB 168MB (59%) +Containers 3 1 0B 0B (0%) +Local Volumes 1 1 22B 0B (0%) + +$ podman system df -v +Images space usage: + +REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNQUE SIZE CONTAINERS +docker.io/library/alpine latest 5cb3aa00f899 2 weeks ago 5.79MB 0B 5.79MB 5 + +Containers space usage: + +CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES +073f7e62812d 5cb3 sleep 100 1 0B About an hourago exited zen_joliot +3f19f5bba242 5cb3 sleep 100 0 5.52kB 4 hoursago exited pedantic_archimedes +8cd89bf645cc 5cb3 ls foodir 0 58B 2 hoursago configured agitated_hamilton +a1d948a4b61d 5cb3 ls foodir 0 12B 2 hoursago exited laughing_wing +eafe3e3c5bb3 5cb3 sleep 10000 0 72B 2 hoursago running priceless_liskov + +Local Volumes space usage: + +VOLUME NAME LINKS SIZE +data 1 0B + +$ podman system df --format "{{.Type}}\t{{.Total}}" +Images 1 +Containers 5 +Local Volumes 1 + +## SEE ALSO +podman-system(1) + +# HISTORY +March 2019, Originally compiled by Qi Wang (qiwan at redhat dot com) diff --git a/docs/podman-system.1.md b/docs/podman-system.1.md index 6d87648e8..32b3efdd9 100644 --- a/docs/podman-system.1.md +++ b/docs/podman-system.1.md @@ -13,7 +13,8 @@ The system command allows you to manage the podman systems | Command | Man Page | Description | | ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- | -| info | [podman-info(1)](podman-info.1.md) | Displays Podman related system information. | +| df | [podman-system-df(1)](podman-system-df.1.md) | Show podman disk usage. | +| info | [podman-system-info(1)](podman-info.1.md) | Displays Podman related system information. | | prune | [podman-system-prune(1)](podman-system-prune.1.md) | Remove all unused data | | renumber | [podman-system-renumber(1)](podman-system-renumber.1.md)| Migrate lock numbers to handle a change in maximum number of locks. | diff --git a/docs/podman-volume-inspect.1.md b/docs/podman-volume-inspect.1.md index 6d5b184ee..c22c80bb7 100644 --- a/docs/podman-volume-inspect.1.md +++ b/docs/podman-volume-inspect.1.md @@ -4,7 +4,7 @@ podman\-volume\-inspect - Inspect one or more volumes ## SYNOPSIS -**podman volume inspect** [*options*] +**podman volume inspect** [*options*] *volume*... ## DESCRIPTION diff --git a/docs/podman-volume-prune.1.md b/docs/podman-volume-prune.1.md index a06bb2fa4..437cad4e5 100644 --- a/docs/podman-volume-prune.1.md +++ b/docs/podman-volume-prune.1.md @@ -4,7 +4,7 @@ podman\-volume\-prune - Remove all unused volumes ## SYNOPSIS -**podman volume rm** [*options*] +**podman volume prune** [*options*] ## DESCRIPTION diff --git a/docs/podman-wait.1.md b/docs/podman-wait.1.md index 672316eef..2d145527b 100644 --- a/docs/podman-wait.1.md +++ b/docs/podman-wait.1.md @@ -1,7 +1,7 @@ % podman-wait "1" ## NAME -podman\-wait - Waits on one or more containers to stop and prints exit code +podman\-wait - Wait on one or more containers to stop and print their exit codes ## SYNOPSIS **podman wait** [*options*] *container* diff --git a/docs/podman.1.md b/docs/podman.1.md index bc03d3c5a..b808a7fa5 100644 --- a/docs/podman.1.md +++ b/docs/podman.1.md @@ -72,6 +72,9 @@ Default state dir is configured in /etc/containers/storage.conf. Name of the OCI runtime as specified in libpod.conf or absolute path to the OCI compatible binary used to run containers. +**--network-cmd-path**=**path** +Path to the command binary to use for setting up a network. It is currently only used for setting up a slirp4netns network. If "" is used then the binary is looked up using the $PATH environment variable. + **--storage-driver**=**value** Storage driver. The default storage driver for UID 0 is configured in /etc/containers/storage.conf (`$HOME/.config/containers/storage.conf` in rootless mode), and is *vfs* for non-root users when *fuse-overlayfs* is not available. The `STORAGE_DRIVER` environment variable overrides the default. The --storage-driver specified driver overrides all. @@ -128,23 +131,25 @@ the exit codes follow the `chroot` standard, see below: | Command | Description | | ----------------------------------------- | ------------------------------------------------------------------------------ | | [podman-attach(1)](podman-attach.1.md) | Attach to a running container. | -| [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. | +| [podman-build(1)](podman-build.1.md) | Build a container image using a Dockerfile. | | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. | -| [podman-container(1)](podman-container.1.md) | Manage Containers. | +| [podman-container(1)](podman-container.1.md) | Manage containers. | | [podman-cp(1)](podman-cp.1.md) | Copy files/folders between a container and the local filesystem. | | [podman-create(1)](podman-create.1.md) | Create a new container. | | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | +| [podman-events(1)](podman-events.1.md) | Monitor Podman events | | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | | [podman-generate(1)](podman-generate.1.md)| Generate structured data based for a containers and pods. | +| [podman-healthcheck(1)](podman-healthcheck.1.md)| Manage healthchecks for containers | | [podman-history(1)](podman-history.1.md) | Show the history of an image. | -| [podman-image(1)](podman-image.1.md) | Manage Images. | +| [podman-image(1)](podman-image.1.md) | Manage images. | | [podman-images(1)](podman-images.1.md) | List images in local storage. | | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | [podman-info(1)](podman-info.1.md) | Displays Podman related system information. | | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | -| [podman-load(1)](podman-load.1.md) | Load an image from the docker archive. | +| [podman-load(1)](podman-load.1.md) | Load an image from a container image archive into container storage. | | [podman-login(1)](podman-login.1.md) | Login to a container registry. | | [podman-logout(1)](podman-logout.1.md) | Logout of a container registry. | | [podman-logs(1)](podman-logs.1.md) | Display the logs of a container. | @@ -152,17 +157,17 @@ the exit codes follow the `chroot` standard, see below: | [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. | | [podman-play(1)](podman-play.1.md) | Play pods and containers based on a structured input file. | | [podman-pod(1)](podman-pod.1.md) | Management tool for groups of containers, called pods. | -| [podman-port(1)](podman-port.1.md) | List port mappings for the container. | +| [podman-port(1)](podman-port.1.md) | List port mappings for a container. | | [podman-ps(1)](podman-ps.1.md) | Prints out information about containers. | | [podman-pull(1)](podman-pull.1.md) | Pull an image from a registry. | | [podman-push(1)](podman-push.1.md) | Push an image from local storage to elsewhere. | | [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. | | [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. | | [podman-rmi(1)](podman-rmi.1.md) | Removes one or more locally stored images. | -| [podman-run(1)](podman-run.1.md) | Run a command in a container. | -| [podman-save(1)](podman-save.1.md) | Save an image to docker-archive or oci. | +| [podman-run(1)](podman-run.1.md) | Run a command in a new container. | +| [podman-save(1)](podman-save.1.md) | Save an image to a container archive. | | [podman-search(1)](podman-search.1.md) | Search a registry for an image. | -| [podman-start(1)](podman-start.1.md) | Starts one or more containers. | +| [podman-start(1)](podman-start.1.md) | Start one or more containers. | | [podman-stats(1)](podman-stats.1.md) | Display a live stream of one or more container's resource usage statistics. | | [podman-stop(1)](podman-stop.1.md) | Stop one or more running containers. | | [podman-system(1)](podman-system.1.md) | Manage podman. | @@ -170,8 +175,8 @@ the exit codes follow the `chroot` standard, see below: | [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | | [podman-umount(1)](podman-umount.1.md) | Unmount a working container's root filesystem. | | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | -| [podman-varlink(1)](podman-varlink.1.md) | Display the Podman version information. | -| [podman-version(1)](podman-version.1.md) | Runs the varlink backend interface. | +| [podman-version(1)](podman-varlink.1.md) | Runs the varlink backend interface. | +| [podman-varlink(1)](podman-version.1.md) | Display the Podman version information. | | [podman-volume(1)](podman-volume.1.md) | Manage Volumes. | | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index 5017e61cd..bfff90016 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -34,7 +34,7 @@ acquire the source, and build it. ```console sudo dnf install -y git runc libassuan-devel golang golang-github-cpuguy83-go-md2man glibc-static \ gpgme-devel glib2-devel device-mapper-devel libseccomp-devel \ - atomic-registries iptables skopeo-containers containernetworking-cni \ + atomic-registries iptables containers-common containernetworking-cni \ conmon ostree-devel ``` ### Building and installing podman diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh index 3f9a3a1bb..d0325b8ae 100755 --- a/hack/get_ci_vm.sh +++ b/hack/get_ci_vm.sh @@ -19,6 +19,7 @@ PROJECT="libpod-218412" GOSRC="/var/tmp/go/src/github.com/containers/libpod" GCLOUD_IMAGE=${GCLOUD_IMAGE:-quay.io/cevich/gcloud_centos:latest} GCLOUD_SUDO=${GCLOUD_SUDO-sudo} +ROOTLESS_USER="madcowdog" # Shared tmp directory between container and us TMPDIR=$(mktemp -d --tmpdir $(basename $0)_tmpdir_XXXXXX) @@ -71,7 +72,9 @@ image_hints() { show_usage() { echo -e "\n${RED}ERROR: $1${NOR}" - echo -e "${YEL}Usage: $(basename $0) [-s | -p] <image_name>${NOR}\n" + echo -e "${YEL}Usage: $(basename $0) [-s | -p | -r] <image_name>${NOR}" + echo "Use -s / -p to select source or package based dependencies" + echo -e "Use -r to setup and run tests as a regular user.\n" if [[ -r ".cirrus.yml" ]] then echo -e "${YEL}Some possible image_name values (from .cirrus.yml):${NOR}" @@ -110,6 +113,10 @@ parse_args(){ echo -e "${RED}Using source-based dependencies.${NOR}" DEPS="PACKAGE_DEPS=false SOURCE_DEPS=true" IMAGE_NAME="$2" + elif [[ "$1" == "-r" ]] + then + DEPS="ROOTLESS_USER=$ROOTLESS_USER" + IMAGE_NAME="$2" else # no -s or -p echo -e "${RED}Using package-based dependencies.${NOR}" DEPS="$(get_env_vars)" @@ -225,4 +232,8 @@ echo -e "\n${YEL}Executing environment setup${NOR}" showrun $SSH_CMD --command "$SETUP_CMD" echo -e "\n${YEL}Connecting to $VMNAME ${RED}(option to delete VM upon logout).${NOR}\n" +if [[ "$1" == "-r" ]] +then + SSH_CMD="$PGCLOUD compute ssh $ROOTLESS_USER@$VMNAME" +fi showrun $SSH_CMD -- -t "cd $GOSRC && exec env $DEPS bash -il" diff --git a/hack/man-page-checker b/hack/man-page-checker new file mode 100755 index 000000000..8e9b5a50d --- /dev/null +++ b/hack/man-page-checker @@ -0,0 +1,105 @@ +#!/bin/bash +# +# man-page-name-checker - validate and cross-reference man page names +# +# FIXME as of 2019-03-20 there are still four files with inconsistent names: +# +# podman-logs.1.md NAME= podman-container-logs +# podman-info.1.md NAME= podman-system-info +# podman-rm.1.md NAME= podman-container-rm +# podman-rmi.1.md NAME= podman-image-rm +# +# If those four get renamed (with suitable symlink fixes), this script +# can be enabled in CI to prevent future inconsistencies. +# + +die() { + echo "$(basename $0): $*" >&2 + exit 1 +} + +cd $(dirname $0)/../docs || die "Please run me from top-level libpod dir" + +rc=0 + +for md in *.1.md;do + # Read the first line after '# NAME' (or '## NAME'). (FIXME: # and ## + # are not the same; should we stick to one convention?) + # There may be more than one name, e.g. podman-info.1.md has + # podman-system-info then another line with podman-info. We + # care only about the first. + name=$(egrep -A1 '^#* NAME' $md|tail -1|awk '{print $1}' | tr -d \\\\) + + if [ "$name" != "$(basename $md .1.md)" ]; then + printf "%-32s NAME= %s\n" $md $name + rc=1 + fi +done + +# Pass 2: compare descriptions. +# +# Make sure the descriptive text in podman-foo.1.md matches the one +# in the table in podman.1.md. +for md in *.1.md;do + desc=$(egrep -A1 '^#* NAME' $md|tail -1|sed -e 's/^podman[^ ]\+ - //') + + # podman.1.md has a two-column table; podman-*.1.md all have three. + parent=$(echo $md | sed -e 's/^\(.*\)-.*$/\1.1.md/') + x=3 + if expr -- "$parent" : ".*-" >/dev/null; then + x=4 + fi + + # Find the descriptive text in the parent man page. + # Strip off the final period; let's not warn about such minutia. + parent_desc=$(grep $md $parent | awk -F'|' "{print \$$x}" | sed -e 's/^ \+//' -e 's/ \+$//' -e 's/\.$//') + + if [ "$desc" != "$parent_desc" ]; then + echo + printf " %-32s = '%s'\n" $md "$desc" + printf " %-32s = '%s'\n" $parent "$parent_desc" + rc=1 + fi +done + +# Pass 3: compare synopses. +# +# Make sure the SYNOPSIS line in podman-foo.1.md reads '**podman foo** ...' +for md in *.1.md;do + # FIXME: several pages have a multi-line form of SYNOPSIS in which + # many or all flags are enumerated. Some of these are trivial + # and really should be made into one line (podman-container-exists, + # container-prune, others); some are more complicated and I + # would still like to see them one-lined (container-runlabel, + # image-trust) but I'm not 100% comfortable doing so myself. + # To view those: + # $ less $(for i in docs/*.1.md;do x=$(grep -A2 '^#* SYNOPSIS' $i|tail -1); if [ -n "$x" ]; then echo $i;fi;done) + # + synopsis=$(egrep -A1 '^#* SYNOPSIS' $md|tail -1) + + # Command name must be bracketed by double asterisks; options and + # arguments are bracketed by single ones. + # E.g. '**podman volume inspect** [*options*] *volume*...' + # Get the command name, and confirm that it matches the md file name. + cmd=$(echo "$synopsis" | sed -e 's/\(.*\)\*\*.*/\1/' | tr -d \* | tr ' ' '-') + if [ "$md" != "$cmd.1.md" ]; then + printf " %-32s SYNOPSIS = %s\n" $md "$cmd" + rc=1 + fi + + # The convention is to use UPPER CASE in 'podman foo --help', + # but *lower case bracketed by asterisks* in the man page + if expr "$synopsis" : ".*[A-Z]" >/dev/null; then + printf " %-32s UPPER-CASE '%s'\n" $md "$synopsis" + rc=1 + fi + + # (for debugging, and getting a sense of standard conventions) + #printf " %-32s ------ '%s'\n" $md "$synopsis" + + # FIXME: some day: run ./bin/podman "args", extract Usage, + # strip off [flags] and [options], then compare arguments +done + + +exit $rc diff --git a/hack/podman-commands.sh b/hack/podman-commands.sh index 754f2923d..eb599763c 100755 --- a/hack/podman-commands.sh +++ b/hack/podman-commands.sh @@ -35,6 +35,9 @@ function podman_man() { # | [podman-cmd(1)\[(podman-cmd.1.md) | Description ... | # For all such, print the 'cmd' portion (the one in brackets). sed -ne 's/^|\s\+\[podman-\([a-z]\+\)(1.*/\1/p' <docs/$1.1.md + + # Special case: there is no podman-help man page, nor need for such. + echo "help" elif [ "$@" = "podman-image-trust" ]; then # Special case: set and show aren't actually in a table in the man page echo set diff --git a/install.md b/install.md index eba1ce8ea..5fe150db2 100644 --- a/install.md +++ b/install.md @@ -107,7 +107,7 @@ yum install -y \ ostree-devel \ pkgconfig \ runc \ - skopeo-containers + containers-common ``` Debian, Ubuntu, and related distributions: @@ -268,7 +268,6 @@ registries = [] # If you need to block pull access from a registry, uncomment the section below # and add the registries fully-qualified name. # -# Docker only [registries.block] registries = [] ``` diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index c226a0617..92a7b1538 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -382,6 +382,11 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { return err } + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + nsBucket, err := getNSBucket(tx) if err != nil { return err @@ -395,41 +400,59 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { // It might not be in our namespace, but // getContainerFromDB() will handle that case. id = []byte(idOrName) - } else { - // They did not give us a full container ID. - // Search for partial ID or full name matches - // Use else-if in case the name is set to a partial ID - exists := false - err = idBucket.ForEach(func(checkID, checkName []byte) error { - // If the container isn't in our namespace, we - // can't match it - if s.namespaceBytes != nil { - ns := nsBucket.Get(checkID) - if !bytes.Equal(ns, s.namespaceBytes) { - return nil - } + return s.getContainerFromDB(id, ctr, ctrBucket) + } + + // Next, check if the full name was given + isPod := false + fullID := namesBucket.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we are not yet certain the ID is a + // container. + ctrExists = ctrBucket.Bucket(fullID) + if ctrExists != nil { + // A container bucket matching the full ID was + // found. + return s.getContainerFromDB(fullID, ctr, ctrBucket) + } + // Don't error if we have a name match but it's not a + // container - there's a chance we have a container with + // an ID starting with those characters. + // However, so we can return a good error, note whether + // this is a pod. + isPod = true + } + + // We were not given a full container ID or name. + // Search for partial ID matches. + exists := false + err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the container isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBucket.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil } - if string(checkName) == idOrName { - if exists { - return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true - } else if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true + } + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(ErrCtrExists, "more than one result for container ID %s", idOrName) } + id = checkID + exists = true + } - return nil - }) - if err != nil { - return err - } else if !exists { - return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName) + return nil + }) + if err != nil { + return err + } else if !exists { + if isPod { + return errors.Wrapf(ErrNoSuchCtr, "%s is a pod, not a container", idOrName) } + return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName) } return s.getContainerFromDB(id, ctr, ctrBucket) @@ -941,6 +964,11 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { return err } + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + nsBkt, err := getNSBucket(tx) if err != nil { return err @@ -954,41 +982,56 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { // It might not be in our namespace, but getPodFromDB() // will handle that case. id = []byte(idOrName) - } else { - // They did not give us a full pod ID. - // Search for partial ID or full name matches - // Use else-if in case the name is set to a partial ID - exists := false - err = idBucket.ForEach(func(checkID, checkName []byte) error { - // If the pod isn't in our namespace, we - // can't match it - if s.namespaceBytes != nil { - ns := nsBkt.Get(checkID) - if !bytes.Equal(ns, s.namespaceBytes) { - return nil - } + return s.getPodFromDB(id, pod, podBkt) + } + + // Next, check if the full name was given + isCtr := false + fullID := namesBkt.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we aren't yet sure if the ID is a pod. + podExists = podBkt.Bucket(fullID) + if podExists != nil { + // A pod bucket matching the full ID was found. + return s.getPodFromDB(fullID, pod, podBkt) + } + // Don't error if we have a name match but it's not a + // pod - there's a chance we have a pod with an ID + // starting with those characters. + // However, so we can return a good error, note whether + // this is a container. + isCtr = true + } + // They did not give us a full pod name or ID. + // Search for partial ID matches. + exists := false + err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the pod isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBkt.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil } - if string(checkName) == idOrName { - if exists { - return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true - } else if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true + } + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) } + id = checkID + exists = true + } - return nil - }) - if err != nil { - return err - } else if !exists { - return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName) + return nil + }) + if err != nil { + return err + } else if !exists { + if isCtr { + return errors.Wrapf(ErrNoSuchPod, "%s is a container, not a pod", idOrName) } + return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName) } // We might have found a container ID, but it's OK diff --git a/libpod/container.go b/libpod/container.go index 75f4a4a4f..ec4e31026 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/image/manifest" "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" @@ -365,6 +366,9 @@ type ContainerConfig struct { // Systemd tells libpod to setup the container in systemd mode Systemd bool `json:"systemd"` + + // HealtchCheckConfig has the health check command and related timings + HealthCheckConfig *manifest.Schema2HealthConfig `json:"healthcheck"` } // ContainerStatus returns a string representation for users @@ -1085,3 +1089,14 @@ func (c *Container) ContainerState() (*ContainerState, error) { deepcopier.Copy(c.state).To(returnConfig) return c.state, nil } + +// HasHealthCheck returns bool as to whether there is a health check +// defined for the container +func (c *Container) HasHealthCheck() bool { + return c.config.HealthCheckConfig != nil +} + +// HealthCheckConfig returns the command and timing attributes of the health check +func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig { + return c.config.HealthCheckConfig +} diff --git a/libpod/container_api.go b/libpod/container_api.go index 6bef3c47d..96435c2ff 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -10,10 +10,11 @@ import ( "time" "github.com/containers/libpod/libpod/driver" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/lookup" "github.com/containers/storage/pkg/stringid" - "github.com/docker/docker/daemon/caps" + "github.com/docker/docker/oci/caps" opentracing "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -125,7 +126,7 @@ func (c *Container) StartAndAttach(ctx context.Context, streams *AttachStreams, } close(attachChan) }() - + c.newContainerEvent(events.Attach) return attachChan, nil } @@ -180,7 +181,7 @@ func (c *Container) StopWithTimeout(timeout uint) error { c.state.State == ContainerStateExited { return ErrCtrStopped } - + defer c.newContainerEvent(events.Stop) return c.stop(timeout) } @@ -198,13 +199,13 @@ func (c *Container) Kill(signal uint) error { if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "can only kill running containers") } - + defer c.newContainerEvent(events.Kill) return c.runtime.ociRuntime.killContainer(c, signal) } // Exec starts a new process inside the container // TODO investigate allowing exec without attaching -func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string, streams *AttachStreams) error { +func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string, streams *AttachStreams, preserveFDs int) error { var capList []string locked := false @@ -266,7 +267,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID) - execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID, streams) + execCmd, err := c.runtime.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID, streams, preserveFDs) if err != nil { return errors.Wrapf(err, "error exec %s", c.ID()) } @@ -321,7 +322,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir // TODO handle this better return errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID()) } - + c.newContainerEvent(events.Exec) logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID()) // Unlock so other processes can use the container @@ -351,7 +352,6 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir if err := c.save(); err != nil { logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err) } - return waitErr } @@ -390,7 +390,7 @@ func (c *Container) Attach(streams *AttachStreams, keys string, resize <-chan re c.state.State != ContainerStateExited { return errors.Wrapf(ErrCtrStateInvalid, "can only attach to created or running containers") } - + defer c.newContainerEvent(events.Attach) return c.attach(streams, keys, resize, false) } @@ -405,7 +405,7 @@ func (c *Container) Mount() (string, error) { return "", err } } - + defer c.newContainerEvent(events.Mount) return c.mount() } @@ -435,6 +435,7 @@ func (c *Container) Unmount(force bool) error { return errors.Wrapf(ErrInternal, "can't unmount %s last mount, it is still in use", c.ID()) } } + defer c.newContainerEvent(events.Unmount) return c.unmount(force) } @@ -455,7 +456,7 @@ func (c *Container) Pause() error { if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, can't pause", c.state.State) } - + defer c.newContainerEvent(events.Pause) return c.pause() } @@ -473,7 +474,7 @@ func (c *Container) Unpause() error { if c.state.State != ContainerStatePaused { return errors.Wrapf(ErrCtrStateInvalid, "%q is not paused, can't unpause", c.ID()) } - + defer c.newContainerEvent(events.Unpause) return c.unpause() } @@ -488,7 +489,7 @@ func (c *Container) Export(path string) error { return err } } - + defer c.newContainerEvent(events.Export) return c.export(path) } @@ -542,7 +543,6 @@ func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) { if err != nil { return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID()) } - return c.getContainerInspectData(size, driverData) } @@ -574,6 +574,7 @@ func (c *Container) WaitWithInterval(waitTimeout time.Duration) (int32, error) { return 0, err } exitCode := c.state.ExitCode + c.newContainerEvent(events.Wait) return exitCode, nil } @@ -597,7 +598,7 @@ func (c *Container) Cleanup(ctx context.Context) error { if len(c.state.ExecSessions) != 0 { return errors.Wrapf(ErrCtrStateInvalid, "container %s has active exec sessions, refusing to clean up", c.ID()) } - + defer c.newContainerEvent(events.Cleanup) return c.cleanup(ctx) } @@ -667,7 +668,7 @@ func (c *Container) Sync() error { } } } - + defer c.newContainerEvent(events.Sync) return nil } @@ -772,7 +773,6 @@ func (c *Container) Refresh(ctx context.Context) error { return err } } - return nil } @@ -800,7 +800,7 @@ func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointO return err } } - + defer c.newContainerEvent(events.Checkpoint) return c.checkpoint(ctx, options) } @@ -815,6 +815,6 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti return err } } - + defer c.newContainerEvent(events.Restore) return c.restore(ctx, options) } diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 5c4fd1a31..0604a550b 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -8,6 +8,7 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/util" is "github.com/containers/image/storage" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -177,5 +178,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai if err != nil { return nil, err } + defer c.newContainerEvent(events.Commit) return c.runtime.imageRuntime.NewFromLocal(id) } diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index e2730c282..aa3a07888 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -93,6 +93,7 @@ func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) HostsPath: hostsPath, StaticDir: config.StaticDir, LogPath: config.LogPath, + ConmonPidFile: config.ConmonPidFile, Name: config.Name, Driver: driverData.Name, MountLabel: config.MountLabel, @@ -127,6 +128,17 @@ func (c *Container) getContainerInspectData(size bool, driverData *inspect.Data) IsInfra: c.IsInfra(), } + if c.config.HealthCheckConfig != nil { + // This container has a healthcheck defined in it; we need to add it's state + healthCheckState, err := c.GetHealthCheckLog() + if err != nil { + // An error here is not considered fatal; no health state will be displayed + logrus.Error(err) + } else { + data.State.Healthcheck = healthCheckState + } + } + // Copy port mappings into network settings if config.PortMappings != nil { data.NetworkSettings.Ports = config.PortMappings diff --git a/libpod/container_internal.go b/libpod/container_internal.go index e3753d825..7a90bc7d4 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/ctime" "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" @@ -34,8 +35,8 @@ const ( ) var ( - // localeToLanguage maps from locale values to language tags. - localeToLanguage = map[string]string{ + // localeToLanguageMap maps from locale values to language tags. + localeToLanguageMap = map[string]string{ "": "und-u-va-posix", "c": "und-u-va-posix", "posix": "und-u-va-posix", @@ -50,6 +51,9 @@ func (c *Container) rootFsSize() (int64, error) { if c.config.Rootfs != "" { return 0, nil } + if c.runtime.store == nil { + return 0, nil + } container, err := c.runtime.store.Container(c.ID()) if err != nil { @@ -210,6 +214,9 @@ func (c *Container) handleExitFile(exitFile string, fi os.FileInfo) error { c.state.Exited = true + // Write an event for the container's death + c.newContainerExitedEvent(c.state.ExitCode) + return nil } @@ -333,11 +340,13 @@ func (c *Container) setupStorage(ctx context.Context) error { } // Set the default Entrypoint and Command - if c.config.Entrypoint == nil { - c.config.Entrypoint = containerInfo.Config.Config.Entrypoint - } - if c.config.Command == nil { - c.config.Command = containerInfo.Config.Config.Cmd + if containerInfo.Config != nil { + if c.config.Entrypoint == nil { + c.config.Entrypoint = containerInfo.Config.Config.Entrypoint + } + if c.config.Command == nil { + c.config.Command = containerInfo.Config.Config.Cmd + } } artifacts := filepath.Join(c.config.StaticDir, artifactsDir) @@ -824,7 +833,13 @@ func (c *Container) init(ctx context.Context) error { if err := c.save(); err != nil { return err } + if c.config.HealthCheckConfig != nil { + if err := c.createTimer(); err != nil { + logrus.Error(err) + } + } + defer c.newContainerEvent(events.Init) return c.completeNetworkSetup() } @@ -947,6 +962,17 @@ func (c *Container) start() error { c.state.State = ContainerStateRunning + if c.config.HealthCheckConfig != nil { + if err := c.updateHealthStatus(HealthCheckStarting); err != nil { + logrus.Error(err) + } + if err := c.startTimer(); err != nil { + logrus.Error(err) + } + } + + defer c.newContainerEvent(events.Start) + return c.save() } @@ -1022,7 +1048,6 @@ func (c *Container) restartWithTimeout(ctx context.Context, timeout uint) (err e return err } } - return c.start() } @@ -1086,7 +1111,7 @@ func (c *Container) cleanupStorage() error { // error // We still want to be able to kick the container out of the // state - if err == storage.ErrNotAContainer || err == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown { logrus.Errorf("Storage for container %s has been removed", c.ID()) return nil } @@ -1113,6 +1138,13 @@ func (c *Container) cleanup(ctx context.Context) error { logrus.Debugf("Cleaning up container %s", c.ID()) + // Remove healthcheck unit/timer file if it execs + if c.config.HealthCheckConfig != nil { + if err := c.removeTimer(); err != nil { + logrus.Error(err) + } + } + // Clean up network namespace, if present if err := c.cleanupNetwork(); err != nil { lastError = err @@ -1231,6 +1263,23 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error) return filepath.Join(c.state.DestinationRunDir, destFile), nil } +// appendStringToRundir appends the provided string to the runtimedir file +func (c *Container) appendStringToRundir(destFile, output string) (string, error) { + destFileName := filepath.Join(c.state.RunDir, destFile) + + f, err := os.OpenFile(destFileName, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return "", errors.Wrapf(err, "unable to open %s", destFileName) + } + defer f.Close() + + if _, err := f.WriteString(output); err != nil { + return "", errors.Wrapf(err, "unable to write %s", destFileName) + } + + return filepath.Join(c.state.DestinationRunDir, destFile), nil +} + // Save OCI spec to disk, replacing any existing specs for the container func (c *Container) saveSpec(spec *spec.Spec) error { // If the OCI spec already exists, we need to replace it @@ -1264,6 +1313,16 @@ func (c *Container) saveSpec(spec *spec.Spec) error { return nil } +// localeToLanguage translates POSIX locale strings to BCP 47 language tags. +func localeToLanguage(locale string) string { + locale = strings.Replace(strings.SplitN(locale, ".", 2)[0], "_", "-", 1) + langString, ok := localeToLanguageMap[strings.ToLower(locale)] + if !ok { + langString = locale + } + return langString +} + // Warning: precreate hooks may alter 'config' in place. func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) { var locale string @@ -1279,11 +1338,7 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten } } - langString, ok := localeToLanguage[strings.ToLower(locale)] - if !ok { - langString = locale - } - + langString := localeToLanguage(locale) lang, err := language.Parse(langString) if err != nil { logrus.Warnf("failed to parse language %q: %s", langString, err) @@ -1399,5 +1454,9 @@ func (c *Container) copyWithTarFromImage(src, dest string) error { } a := archive.NewDefaultArchiver() source := filepath.Join(mountpoint, src) + + if err = c.copyOwnerAndPerms(source, dest); err != nil { + return err + } return a.CopyWithTar(source, dest) } diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index b074efa3a..c6c9ceb0c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -18,14 +18,15 @@ import ( cnitypes "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" + "github.com/containers/buildah/pkg/secrets" crioAnnotations "github.com/containers/libpod/pkg/annotations" "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/criu" "github.com/containers/libpod/pkg/lookup" "github.com/containers/libpod/pkg/resolvconf" "github.com/containers/libpod/pkg/rootless" - "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage/pkg/idtools" + "github.com/cyphar/filepath-securejoin" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -202,7 +203,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } // 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 _, m := range g.Mounts() { + for i := range g.Config.Mounts { + m := &g.Config.Mounts[i] var options []string for _, o := range m.Options { switch o { @@ -218,6 +220,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } m.Options = options + + // If we are using a user namespace, we will use an intermediate + // directory to bind mount volumes + if c.state.UserNSRoot != "" && strings.HasPrefix(m.Source, c.runtime.config.VolumePath) { + newSourceDir := filepath.Join(c.state.UserNSRoot, "volumes") + m.Source = strings.Replace(m.Source, c.runtime.config.VolumePath, newSourceDir, 1) + } } g.SetProcessSelinuxLabel(c.ProcessLabel()) @@ -366,6 +375,18 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { // For private volumes any root propagation value should work. rootPropagation := "" for _, m := range mounts { + // We need to remove all symlinks from tmpfs mounts. + // Runc and other runtimes may choke on them. + // Easy solution: use securejoin to do a scoped evaluation of + // the links, then trim off the mount prefix. + if m.Type == "tmpfs" { + finalPath, err := securejoin.SecureJoin(c.state.Mountpoint, m.Destination) + if err != nil { + return nil, errors.Wrapf(err, "error resolving symlinks for mount destination %s", m.Destination) + } + trimmedPath := strings.TrimPrefix(finalPath, strings.TrimSuffix(c.state.Mountpoint, "/")) + m.Destination = trimmedPath + } g.AddMount(m) for _, opt := range m.Options { switch opt { @@ -472,10 +493,19 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr return nil } -func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { - +func (c *Container) checkpointRestoreSupported() (err error) { if !criu.CheckForCriu() { - return errors.Errorf("checkpointing a container requires at least CRIU %d", criu.MinCriuVersion) + return errors.Errorf("Checkpoint/Restore requires at least CRIU %d", criu.MinCriuVersion) + } + if !c.runtime.ociRuntime.featureCheckCheckpointing() { + return errors.Errorf("Configured runtime does not support checkpoint/restore") + } + return nil +} + +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { + if err := c.checkpointRestoreSupported(); err != nil { + return err } if c.state.State != ContainerStateRunning { @@ -532,8 +562,8 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) (err error) { - if !criu.CheckForCriu() { - return errors.Errorf("restoring a container requires at least CRIU %d", criu.MinCriuVersion) + if err := c.checkpointRestoreSupported(); err != nil { + return err } if (c.state.State != ContainerStateConfigured) && (c.state.State != ContainerStateExited) { @@ -656,18 +686,21 @@ func (c *Container) makeBindMounts() error { if !netDisabled { // If /etc/resolv.conf and /etc/hosts exist, delete them so we - // will recreate - if path, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { - if err := os.Remove(path); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing container %s resolv.conf", c.ID()) + // will recreate. Only do this if we aren't sharing them with + // another container. + if c.config.NetNsCtr == "" { + if path, ok := c.state.BindMounts["/etc/resolv.conf"]; ok { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing container %s resolv.conf", c.ID()) + } + delete(c.state.BindMounts, "/etc/resolv.conf") } - delete(c.state.BindMounts, "/etc/resolv.conf") - } - if path, ok := c.state.BindMounts["/etc/hosts"]; ok { - if err := os.Remove(path); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing container %s hosts", c.ID()) + if path, ok := c.state.BindMounts["/etc/hosts"]; ok { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing container %s hosts", c.ID()) + } + delete(c.state.BindMounts, "/etc/hosts") } - delete(c.state.BindMounts, "/etc/hosts") } if c.config.NetNsCtr != "" { @@ -689,13 +722,29 @@ func (c *Container) makeBindMounts() error { // If it doesn't, don't copy them resolvPath, exists := bindMounts["/etc/resolv.conf"] if exists { - c.state.BindMounts["/etc/resolv.conf"] = resolvPath } + + // check if dependency container has an /etc/hosts file hostsPath, exists := bindMounts["/etc/hosts"] - if exists { - c.state.BindMounts["/etc/hosts"] = hostsPath + if !exists { + return errors.Errorf("error finding hosts file of dependency container %s for container %s", depCtr.ID(), c.ID()) + } + + depCtr.lock.Lock() + // generate a hosts file for the dependency container, + // based on either its old hosts file, or the default, + // and add the relevant information from the new container (hosts and IP) + hostsPath, err = depCtr.appendHosts(hostsPath, c) + + if err != nil { + depCtr.lock.Unlock() + return errors.Wrapf(err, "error creating hosts file for container %s which depends on container %s", c.ID(), depCtr.ID()) } + depCtr.lock.Unlock() + + // finally, save it in the new container + c.state.BindMounts["/etc/hosts"] = hostsPath } else { newResolv, err := c.generateResolvConf() if err != nil { @@ -703,7 +752,7 @@ func (c *Container) makeBindMounts() error { } c.state.BindMounts["/etc/resolv.conf"] = newResolv - newHosts, err := c.generateHosts() + newHosts, err := c.generateHosts("/etc/hosts") if err != nil { return errors.Wrapf(err, "error creating hosts file for container %s", c.ID()) } @@ -807,6 +856,10 @@ func (c *Container) generateResolvConf() (string, error) { // Make a new resolv.conf nameservers := resolvconf.GetNameservers(resolv.Content) + // slirp4netns has a built in DNS server. + if c.config.NetMode.IsSlirp4netns() { + nameservers = append(nameservers, "10.0.2.3") + } if len(c.config.DNSServer) > 0 { // We store DNS servers as net.IP, so need to convert to string nameservers = []string{} @@ -845,12 +898,28 @@ func (c *Container) generateResolvConf() (string, error) { } // generateHosts creates a containers hosts file -func (c *Container) generateHosts() (string, error) { - orig, err := ioutil.ReadFile("/etc/hosts") +func (c *Container) generateHosts(path string) (string, error) { + orig, err := ioutil.ReadFile(path) if err != nil { - return "", errors.Wrapf(err, "unable to read /etc/hosts") + return "", errors.Wrapf(err, "unable to read %s", path) } hosts := string(orig) + hosts += c.getHosts() + return c.writeStringToRundir("hosts", hosts) +} + +// appendHosts appends a container's config and state pertaining to hosts to a container's +// local hosts file. netCtr is the container from which the netNS information is +// taken. +// path is the basis of the hosts file, into which netCtr's netNS information will be appended. +func (c *Container) appendHosts(path string, netCtr *Container) (string, error) { + return c.appendStringToRundir("hosts", netCtr.getHosts()) +} + +// getHosts finds the pertinent information for a container's host file in its config and state +// and returns a string in a format that can be written to the host file +func (c *Container) getHosts() string { + var hosts string if len(c.config.HostAdd) > 0 { for _, host := range c.config.HostAdd { // the host format has already been verified at this point @@ -862,7 +931,7 @@ func (c *Container) generateHosts() (string, error) { ipAddress := strings.Split(c.state.NetworkStatus[0].IPs[0].Address.String(), "/")[0] hosts += fmt.Sprintf("%s\t%s\n", ipAddress, c.Hostname()) } - return c.writeStringToRundir("hosts", hosts) + return hosts } // generatePasswd generates a container specific passwd file, @@ -921,3 +990,20 @@ func (c *Container) generatePasswd() (string, error) { } return passwdFile, nil } + +func (c *Container) copyOwnerAndPerms(source, dest string) error { + info, err := os.Stat(source) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return errors.Wrapf(err, "cannot stat `%s`", dest) + } + if err := os.Chmod(dest, info.Mode()); err != nil { + return errors.Wrapf(err, "cannot chmod `%s`", dest) + } + if err := os.Chown(dest, int(info.Sys().(*syscall.Stat_t).Uid), int(info.Sys().(*syscall.Stat_t).Gid)); err != nil { + return errors.Wrapf(err, "cannot chown `%s`", dest) + } + return nil +} diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go index f1e2b70a7..1654af929 100644 --- a/libpod/container_internal_test.go +++ b/libpod/container_internal_test.go @@ -17,6 +17,54 @@ import ( // hookPath is the path to an example hook executable. var hookPath string +func TestLocaleToLanguage(t *testing.T) { + for _, testCase := range []struct { + locale string + language string + }{ + { + locale: "", + language: "und-u-va-posix", + }, + { + locale: "C", + language: "und-u-va-posix", + }, + { + locale: "POSIX", + language: "und-u-va-posix", + }, + { + locale: "c", + language: "und-u-va-posix", + }, + { + locale: "en", + language: "en", + }, + { + locale: "en_US", + language: "en-US", + }, + { + locale: "en.UTF-8", + language: "en", + }, + { + locale: "en_US.UTF-8", + language: "en-US", + }, + { + locale: "does-not-exist", + language: "does-not-exist", + }, + } { + t.Run(testCase.locale, func(t *testing.T) { + assert.Equal(t, testCase.language, localeToLanguage(testCase.locale)) + }) + } +} + func TestPostDeleteHooks(t *testing.T) { ctx := context.Background() dir, err := ioutil.TempDir("", "libpod_test_") diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 4af0cd56c..f707b350c 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -35,3 +35,7 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO func (c *Container) restore(ctx context.Context, options ContainerCheckpointOptions) error { return ErrNotImplemented } + +func (c *Container) copyOwnerAndPerms(source, dest string) error { + return nil +} diff --git a/libpod/container_log.go b/libpod/container_log.go new file mode 100644 index 000000000..e998ad316 --- /dev/null +++ b/libpod/container_log.go @@ -0,0 +1,213 @@ +package libpod + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + "sync" + "time" + + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // logTimeFormat is the time format used in the log. + // It is a modified version of RFC3339Nano that guarantees trailing + // zeroes are not trimmed, taken from + // https://github.com/golang/go/issues/19635 + logTimeFormat = "2006-01-02T15:04:05.000000000Z07:00" +) + +// LogOptions is the options you can use for logs +type LogOptions struct { + Details bool + Follow bool + Since time.Time + Tail uint64 + Timestamps bool + Multi bool + WaitGroup *sync.WaitGroup +} + +// LogLine describes the information for each line of a log +type LogLine struct { + Device string + ParseLogType string + Time time.Time + Msg string + CID string +} + +// Log is a runtime function that can read one or more container logs. +func (r *Runtime) Log(containers []*Container, options *LogOptions, logChannel chan *LogLine) error { + for _, ctr := range containers { + if err := ctr.ReadLog(options, logChannel); err != nil { + return err + } + } + return nil +} + +// ReadLog reads a containers log based on the input options and returns loglines over a channel +func (c *Container) ReadLog(options *LogOptions, logChannel chan *LogLine) error { + t, tailLog, err := getLogFile(c.LogPath(), options) + if err != nil { + // If the log file does not exist, this is not fatal. + if os.IsNotExist(errors.Cause(err)) { + return nil + } + return errors.Wrapf(err, "unable to read log file %s for %s ", c.ID(), c.LogPath()) + } + options.WaitGroup.Add(1) + if len(tailLog) > 0 { + for _, nll := range tailLog { + nll.CID = c.ID() + if nll.Since(options.Since) { + logChannel <- nll + } + } + } + + go func() { + var partial string + for line := range t.Lines { + nll, err := newLogLine(line.Text) + if err != nil { + logrus.Error(err) + continue + } + if nll.Partial() { + partial = partial + nll.Msg + continue + } else if !nll.Partial() && len(partial) > 1 { + nll.Msg = partial + partial = "" + } + nll.CID = c.ID() + if nll.Since(options.Since) { + logChannel <- nll + } + } + options.WaitGroup.Done() + }() + return nil +} + +// getLogFile returns an hp tail for a container given options +func getLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error) { + var ( + whence int + err error + logTail []*LogLine + ) + // whence 0=origin, 2=end + if options.Tail > 0 { + whence = 2 + logTail, err = getTailLog(path, int(options.Tail)) + if err != nil { + return nil, nil, err + } + } + seek := tail.SeekInfo{ + Offset: 0, + Whence: whence, + } + + t, err := tail.TailFile(path, tail.Config{MustExist: true, Poll: true, Follow: options.Follow, Location: &seek, Logger: tail.DiscardingLogger}) + return t, logTail, err +} + +func getTailLog(path string, tail int) ([]*LogLine, error) { + var ( + tailLog []*LogLine + nlls []*LogLine + tailCounter int + partial string + ) + content, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + splitContent := strings.Split(string(content), "\n") + // We read the content in reverse and add each nll until we have the same + // number of F type messages as the desired tail + for i := len(splitContent) - 1; i >= 0; i-- { + if len(splitContent[i]) == 0 { + continue + } + nll, err := newLogLine(splitContent[i]) + if err != nil { + return nil, err + } + nlls = append(nlls, nll) + if !nll.Partial() { + tailCounter = tailCounter + 1 + } + if tailCounter == tail { + break + } + } + // Now we iterate the results and assemble partial messages to become full messages + for _, nll := range nlls { + if nll.Partial() { + partial = partial + nll.Msg + } else { + nll.Msg = nll.Msg + partial + tailLog = append(tailLog, nll) + partial = "" + } + } + return tailLog, nil +} + +// String converts a logline to a string for output given whether a detail +// bool is specified. +func (l *LogLine) String(options *LogOptions) string { + var out string + if options.Multi { + cid := l.CID + if len(cid) > 12 { + cid = cid[:12] + } + out = fmt.Sprintf("%s ", cid) + } + if options.Timestamps { + out = out + fmt.Sprintf("%s ", l.Time.Format(logTimeFormat)) + } + return out + l.Msg +} + +// Since returns a bool as to whether a log line occurred after a given time +func (l *LogLine) Since(since time.Time) bool { + return l.Time.After(since) +} + +// newLogLine creates a logLine struct from a container log string +func newLogLine(line string) (*LogLine, error) { + splitLine := strings.Split(line, " ") + if len(splitLine) < 4 { + return nil, errors.Errorf("'%s' is not a valid container log line", line) + } + logTime, err := time.Parse(time.RFC3339Nano, splitLine[0]) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert time %s from container log", splitLine[0]) + } + l := LogLine{ + Time: logTime, + Device: splitLine[1], + ParseLogType: splitLine[2], + Msg: strings.Join(splitLine[3:], " "), + } + return &l, nil +} + +// Partial returns a bool if the log line is a partial log type +func (l *LogLine) Partial() bool { + if l.ParseLogType == "P" { + return true + } + return false +} diff --git a/libpod/events.go b/libpod/events.go new file mode 100644 index 000000000..139600982 --- /dev/null +++ b/libpod/events.go @@ -0,0 +1,96 @@ +package libpod + +import ( + "os" + + "github.com/containers/libpod/libpod/events" + "github.com/hpcloud/tail" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// newContainerEvent creates a new event based on a container +func (c *Container) newContainerEvent(status events.Status) { + e := events.NewEvent(status) + e.ID = c.ID() + e.Name = c.Name() + e.Image = c.config.RootfsImageName + e.Type = events.Container + if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { + logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + } +} + +// newContainerExitedEvent creates a new event for a container's death +func (c *Container) newContainerExitedEvent(exitCode int32) { + e := events.NewEvent(events.Exited) + e.ID = c.ID() + e.Name = c.Name() + e.Image = c.config.RootfsImageName + e.Type = events.Container + e.ContainerExitCode = int(exitCode) + if err := e.Write(c.runtime.config.EventsLogFilePath); err != nil { + logrus.Errorf("unable to write event to %s", c.runtime.config.EventsLogFilePath) + } +} + +// newPodEvent creates a new event for a libpod pod +func (p *Pod) newPodEvent(status events.Status) { + e := events.NewEvent(status) + e.ID = p.ID() + e.Name = p.Name() + e.Type = events.Pod + if err := e.Write(p.runtime.config.EventsLogFilePath); err != nil { + logrus.Errorf("unable to write event to %s", p.runtime.config.EventsLogFilePath) + } +} + +// newVolumeEvent creates a new event for a libpod volume +func (v *Volume) newVolumeEvent(status events.Status) { + e := events.NewEvent(status) + e.Name = v.Name() + e.Type = events.Volume + if err := e.Write(v.runtime.config.EventsLogFilePath); err != nil { + logrus.Errorf("unable to write event to %s", v.runtime.config.EventsLogFilePath) + } +} + +// Events is a wrapper function for everyone to begin tailing the events log +// with options +func (r *Runtime) Events(fromStart, stream bool, options []events.EventFilter, eventChannel chan *events.Event) error { + t, err := r.getTail(fromStart, stream) + if err != nil { + return err + } + for line := range t.Lines { + event, err := events.NewEventFromString(line.Text) + if err != nil { + return err + } + switch event.Type { + case events.Image, events.Volume, events.Pod, events.Container: + // no-op + default: + return errors.Errorf("event type %s is not valid in %s", event.Type.String(), r.GetConfig().EventsLogFilePath) + } + include := true + for _, filter := range options { + include = include && filter(event) + } + if include { + eventChannel <- event + } + } + close(eventChannel) + return nil +} + +func (r *Runtime) getTail(fromStart, stream bool) (*tail.Tail, error) { + reopen := true + seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} + if fromStart || !stream { + seek.Whence = 0 + reopen = false + } + return tail.TailFile(r.config.EventsLogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger}) +} diff --git a/libpod/events/events.go b/libpod/events/events.go new file mode 100644 index 000000000..7db36653e --- /dev/null +++ b/libpod/events/events.go @@ -0,0 +1,268 @@ +package events + +import ( + "encoding/json" + "fmt" + "os" + "time" + + "github.com/pkg/errors" +) + +// Event describes the attributes of a libpod event +type Event struct { + // ContainerExitCode is for storing the exit code of a container which can + // be used for "internal" event notification + ContainerExitCode int + // ID can be for the container, image, volume, etc + ID string + // Image used where applicable + Image string + // Name where applicable + Name string + // Status describes the event that occurred + Status Status + // Time the event occurred + Time time.Time + // Type of event that occurred + Type Type +} + +// Type of event that occurred (container, volume, image, pod, etc) +type Type string + +// Status describes the actual event action (stop, start, create, kill) +type Status string + +const ( + // If you add or subtract any values to the following lists, make sure you also update + // the switch statements below and the enums for EventType or EventStatus in the + // varlink description file. + + // Container - event is related to containers + Container Type = "container" + // Image - event is related to images + Image Type = "image" + // Pod - event is related to pods + Pod Type = "pod" + // Volume - event is related to volumes + Volume Type = "volume" + + // Attach ... + Attach Status = "attach" + // Checkpoint ... + Checkpoint Status = "checkpoint" + // Cleanup ... + Cleanup Status = "cleanup" + // Commit ... + Commit Status = "commit" + // Create ... + Create Status = "create" + // Exec ... + Exec Status = "exec" + // Exited indicates that a container's process died + Exited Status = "died" + // Export ... + Export Status = "export" + // History ... + History Status = "history" + // Import ... + Import Status = "import" + // Init ... + Init Status = "init" + // Kill ... + Kill Status = "kill" + // LoadFromArchive ... + LoadFromArchive Status = "status" + // Mount ... + Mount Status = "mount" + // Pause ... + Pause Status = "pause" + // Prune ... + Prune Status = "prune" + // Pull ... + Pull Status = "pull" + // Push ... + Push Status = "push" + // Remove ... + Remove Status = "remove" + // Restore ... + Restore Status = "restore" + // Save ... + Save Status = "save" + // Start ... + Start Status = "start" + // Stop ... + Stop Status = "stop" + // Sync ... + Sync Status = "sync" + // Tag ... + Tag Status = "tag" + // Unmount ... + Unmount Status = "unmount" + // Unpause ... + Unpause Status = "unpause" + // Untag ... + Untag Status = "untag" + // Wait ... + Wait Status = "wait" +) + +// EventFilter for filtering events +type EventFilter func(*Event) bool + +// NewEvent creates a event struct and populates with +// the given status and time. +func NewEvent(status Status) Event { + return Event{ + Status: status, + Time: time.Now(), + } +} + +// Write will record the event to the given path +func (e *Event) Write(path string) error { + f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) + if err != nil { + return err + } + defer f.Close() + eventJSONString, err := e.ToJSONString() + if err != nil { + return err + } + if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { + return err + } + return nil +} + +// Recycle checks if the event log has reach a limit and if so +// renames the current log and starts a new one. The remove bool +// indicates the old log file should be deleted. +func (e *Event) Recycle(path string, remove bool) error { + return errors.New("not implemented") +} + +// ToJSONString returns the event as a json'ified string +func (e *Event) ToJSONString() (string, error) { + b, err := json.Marshal(e) + return string(b), err +} + +// ToHumanReadable returns human readable event as a formatted string +func (e *Event) ToHumanReadable() string { + var humanFormat string + switch e.Type { + case Container, Pod: + humanFormat = fmt.Sprintf("%s %s %s %s (image=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.Image, e.Name) + case Image: + humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name) + case Volume: + humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) + } + return humanFormat +} + +// NewEventFromString takes stringified json and converts +// it to an event +func NewEventFromString(event string) (*Event, error) { + e := Event{} + if err := json.Unmarshal([]byte(event), &e); err != nil { + return nil, err + } + return &e, nil + +} + +// ToString converts a Type to a string +func (t Type) String() string { + return string(t) +} + +// ToString converts a status to a string +func (s Status) String() string { + return string(s) +} + +// StringToType converts string to an EventType +func StringToType(name string) (Type, error) { + switch name { + case Container.String(): + return Container, nil + case Image.String(): + return Image, nil + case Pod.String(): + return Pod, nil + case Volume.String(): + return Volume, nil + } + return "", errors.Errorf("unknown event type %s", name) +} + +// StringToStatus converts a string to an Event Status +// TODO if we add more events, we might consider a go-generator to +// create the switch statement +func StringToStatus(name string) (Status, error) { + switch name { + case Attach.String(): + return Attach, nil + case Checkpoint.String(): + return Checkpoint, nil + case Restore.String(): + return Restore, nil + case Cleanup.String(): + return Cleanup, nil + case Commit.String(): + return Commit, nil + case Create.String(): + return Create, nil + case Exec.String(): + return Exec, nil + case Exited.String(): + return Exited, nil + case Export.String(): + return Export, nil + case History.String(): + return History, nil + case Import.String(): + return Import, nil + case Init.String(): + return Init, nil + case Kill.String(): + return Kill, nil + case LoadFromArchive.String(): + return LoadFromArchive, nil + case Mount.String(): + return Mount, nil + case Pause.String(): + return Pause, nil + case Prune.String(): + return Prune, nil + case Pull.String(): + return Pull, nil + case Push.String(): + return Push, nil + case Remove.String(): + return Remove, nil + case Save.String(): + return Save, nil + case Start.String(): + return Start, nil + case Stop.String(): + return Stop, nil + case Sync.String(): + return Sync, nil + case Tag.String(): + return Tag, nil + case Unmount.String(): + return Unmount, nil + case Unpause.String(): + return Unpause, nil + case Untag.String(): + return Untag, nil + case Wait.String(): + return Wait, nil + } + return "", errors.Errorf("unknown event status %s", name) +} diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go new file mode 100644 index 000000000..d8f56860b --- /dev/null +++ b/libpod/healthcheck.go @@ -0,0 +1,320 @@ +package libpod + +import ( + "bufio" + "bytes" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/containers/libpod/pkg/inspect" + "github.com/coreos/go-systemd/dbus" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// HealthCheckStatus represents the current state of a container +type HealthCheckStatus int + +const ( + // HealthCheckSuccess means the health worked + HealthCheckSuccess HealthCheckStatus = iota + // HealthCheckFailure means the health ran and failed + HealthCheckFailure HealthCheckStatus = iota + // HealthCheckContainerStopped means the health check cannot + // be run because the container is stopped + HealthCheckContainerStopped HealthCheckStatus = iota + // HealthCheckContainerNotFound means the container could + // not be found in local store + HealthCheckContainerNotFound HealthCheckStatus = iota + // HealthCheckNotDefined means the container has no health + // check defined in it + HealthCheckNotDefined HealthCheckStatus = iota + // HealthCheckInternalError means somes something failed obtaining or running + // a given health check + HealthCheckInternalError HealthCheckStatus = iota + // HealthCheckDefined means the healthcheck was found on the container + HealthCheckDefined HealthCheckStatus = iota + + // MaxHealthCheckNumberLogs is the maximum number of attempts we keep + // in the healtcheck history file + MaxHealthCheckNumberLogs int = 5 + // MaxHealthCheckLogLength in characters + MaxHealthCheckLogLength = 500 + + // HealthCheckHealthy describes a healthy container + HealthCheckHealthy string = "healthy" + // HealthCheckUnhealthy describes an unhealthy container + HealthCheckUnhealthy string = "unhealthy" + // HealthCheckStarting describes the time between when the container starts + // and the start-period (time allowed for the container to start and application + // to be running) expires. + HealthCheckStarting string = "starting" +) + +// hcWriteCloser allows us to use bufio as a WriteCloser +type hcWriteCloser struct { + *bufio.Writer +} + +// Used to add a closer to bufio +func (hcwc hcWriteCloser) Close() error { + return nil +} + +// HealthCheck verifies the state and validity of the healthcheck configuration +// on the container and then executes the healthcheck +func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) { + container, err := r.LookupContainer(name) + if err != nil { + return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name) + } + hcStatus, err := checkHealthCheckCanBeRun(container) + if err == nil { + return container.runHealthCheck() + } + return hcStatus, err +} + +// runHealthCheck runs the health check as defined by the container +func (c *Container) runHealthCheck() (HealthCheckStatus, error) { + var ( + newCommand []string + returnCode int + capture bytes.Buffer + inStartPeriod bool + ) + hcStatus, err := checkHealthCheckCanBeRun(c) + if err != nil { + return hcStatus, err + } + hcCommand := c.HealthCheckConfig().Test + if len(hcCommand) > 0 && hcCommand[0] == "CMD-SHELL" { + newCommand = []string{"sh", "-c", strings.Join(hcCommand[1:], " ")} + } else { + newCommand = hcCommand + } + captureBuffer := bufio.NewWriter(&capture) + hcw := hcWriteCloser{ + captureBuffer, + } + streams := new(AttachStreams) + streams.OutputStream = hcw + streams.ErrorStream = hcw + streams.InputStream = os.Stdin + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID()) + timeStart := time.Now() + hcResult := HealthCheckSuccess + hcErr := c.Exec(false, false, []string{}, newCommand, "", "", streams, 0) + if hcErr != nil { + hcResult = HealthCheckFailure + returnCode = 1 + } + timeEnd := time.Now() + if c.HealthCheckConfig().StartPeriod > 0 { + // there is a start-period we need to honor; we add startPeriod to container start time + startPeriodTime := c.state.StartedTime.Add(c.HealthCheckConfig().StartPeriod) + if timeStart.Before(startPeriodTime) { + // we are still in the start period, flip the inStartPeriod bool + inStartPeriod = true + logrus.Debugf("healthcheck for %s being run in start-period", c.ID()) + } + } + + eventLog := capture.String() + if len(eventLog) > MaxHealthCheckLogLength { + eventLog = eventLog[:MaxHealthCheckLogLength] + } + + if timeEnd.Sub(timeStart) > c.HealthCheckConfig().Timeout { + returnCode = -1 + hcResult = HealthCheckFailure + hcErr = errors.Errorf("healthcheck command exceeded timeout of %s", c.HealthCheckConfig().Timeout.String()) + } + hcl := newHealthCheckLog(timeStart, timeEnd, returnCode, eventLog) + if err := c.updateHealthCheckLog(hcl, inStartPeriod); err != nil { + return hcResult, errors.Wrapf(err, "unable to update health check log %s for %s", c.healthCheckLogPath(), c.ID()) + } + return hcResult, hcErr +} + +func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) { + cstate, err := c.State() + if err != nil { + return HealthCheckInternalError, err + } + if cstate != ContainerStateRunning { + return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID()) + } + if !c.HasHealthCheck() { + return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + } + return HealthCheckDefined, nil +} + +func newHealthCheckLog(start, end time.Time, exitCode int, log string) inspect.HealthCheckLog { + return inspect.HealthCheckLog{ + Start: start.Format(time.RFC3339Nano), + End: end.Format(time.RFC3339Nano), + ExitCode: exitCode, + Output: log, + } +} + +// updatedHealthCheckStatus updates the health status of the container +// in the healthcheck log +func (c *Container) updateHealthStatus(status string) error { + healthCheck, err := c.GetHealthCheckLog() + if err != nil { + return err + } + healthCheck.Status = status + newResults, err := json.Marshal(healthCheck) + if err != nil { + return errors.Wrapf(err, "unable to marshall healthchecks for writing status") + } + return ioutil.WriteFile(c.healthCheckLogPath(), newResults, 0700) +} + +// UpdateHealthCheckLog parses the health check results and writes the log +func (c *Container) updateHealthCheckLog(hcl inspect.HealthCheckLog, inStartPeriod bool) error { + healthCheck, err := c.GetHealthCheckLog() + if err != nil { + return err + } + if hcl.ExitCode == 0 { + // set status to healthy, reset failing state to 0 + healthCheck.Status = HealthCheckHealthy + healthCheck.FailingStreak = 0 + } else { + if len(healthCheck.Status) < 1 { + healthCheck.Status = HealthCheckHealthy + } + if !inStartPeriod { + // increment failing streak + healthCheck.FailingStreak = healthCheck.FailingStreak + 1 + // if failing streak > retries, then status to unhealthy + if int(healthCheck.FailingStreak) >= c.HealthCheckConfig().Retries { + healthCheck.Status = HealthCheckUnhealthy + } + } + } + healthCheck.Log = append(healthCheck.Log, hcl) + if len(healthCheck.Log) > MaxHealthCheckNumberLogs { + healthCheck.Log = healthCheck.Log[1:] + } + newResults, err := json.Marshal(healthCheck) + if err != nil { + return errors.Wrapf(err, "unable to marshall healthchecks for writing") + } + return ioutil.WriteFile(c.healthCheckLogPath(), newResults, 0700) +} + +// HealthCheckLogPath returns the path for where the health check log is +func (c *Container) healthCheckLogPath() string { + return filepath.Join(filepath.Dir(c.LogPath()), "healthcheck.log") +} + +// GetHealthCheckLog returns HealthCheck results by reading the container's +// health check log file. If the health check log file does not exist, then +// an empty healthcheck struct is returned +func (c *Container) GetHealthCheckLog() (inspect.HealthCheckResults, error) { + var healthCheck inspect.HealthCheckResults + if _, err := os.Stat(c.healthCheckLogPath()); os.IsNotExist(err) { + return healthCheck, nil + } + b, err := ioutil.ReadFile(c.healthCheckLogPath()) + if err != nil { + return healthCheck, errors.Wrapf(err, "failed to read health check log file %s", c.healthCheckLogPath()) + } + if err := json.Unmarshal(b, &healthCheck); err != nil { + return healthCheck, errors.Wrapf(err, "failed to unmarshal existing healthcheck results in %s", c.healthCheckLogPath()) + } + return healthCheck, nil +} + +// createTimer systemd timers for healthchecks of a container +func (c *Container) createTimer() error { + if c.disableHealthCheckSystemd() { + return nil + } + podman, err := os.Executable() + if err != nil { + return errors.Wrapf(err, "failed to get path for podman for a health check timer") + } + + var cmd = []string{"--unit", fmt.Sprintf("%s", c.ID()), fmt.Sprintf("--on-unit-inactive=%s", c.HealthCheckConfig().Interval.String()), "--timer-property=AccuracySec=1s", podman, "healthcheck", "run", c.ID()} + + conn, err := dbus.NewSystemdConnection() + if err != nil { + return errors.Wrapf(err, "unable to get systemd connection to add healthchecks") + } + conn.Close() + logrus.Debugf("creating systemd-transient files: %s %s", "systemd-run", cmd) + systemdRun := exec.Command("systemd-run", cmd...) + _, err = systemdRun.CombinedOutput() + if err != nil { + return err + } + return nil +} + +// startTimer starts a systemd timer for the healthchecks +func (c *Container) startTimer() error { + if c.disableHealthCheckSystemd() { + return nil + } + conn, err := dbus.NewSystemdConnection() + if err != nil { + return errors.Wrapf(err, "unable to get systemd connection to start healthchecks") + } + defer conn.Close() + _, err = conn.StartUnit(fmt.Sprintf("%s.service", c.ID()), "fail", nil) + return err +} + +// removeTimer removes the systemd timer and unit files +// for the container +func (c *Container) removeTimer() error { + if c.disableHealthCheckSystemd() { + return nil + } + conn, err := dbus.NewSystemdConnection() + if err != nil { + return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks") + } + defer conn.Close() + serviceFile := fmt.Sprintf("%s.timer", c.ID()) + _, err = conn.StopUnit(serviceFile, "fail", nil) + return err +} + +// HealthCheckStatus returns the current state of a container with a healthcheck +func (c *Container) HealthCheckStatus() (string, error) { + if !c.HasHealthCheck() { + return "", errors.Errorf("container %s has no defined healthcheck", c.ID()) + } + results, err := c.GetHealthCheckLog() + if err != nil { + return "", errors.Wrapf(err, "unable to get healthcheck log for %s", c.ID()) + } + return results.Status, nil +} + +func (c *Container) disableHealthCheckSystemd() bool { + if os.Getenv("DISABLE_HC_SYSTEMD") == "true" { + return true + } + if c.config.HealthCheckConfig.Interval == 0 { + return true + } + return false +} diff --git a/libpod/image/image.go b/libpod/image/image.go index b20419d7b..f79bc3dc2 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -24,12 +24,13 @@ import ( "github.com/containers/image/types" "github.com/containers/libpod/libpod/common" "github.com/containers/libpod/libpod/driver" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/inspect" "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" - digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opentracing/opentracing-go" @@ -64,6 +65,7 @@ type Image struct { type Runtime struct { store storage.Store SignaturePolicyPath string + EventsLogFilePath string } // ErrRepoTagNotFound is the error returned when the image id given doesn't match a rep tag in store @@ -195,7 +197,7 @@ func (ir *Runtime) LoadFromArchiveReference(ctx context.Context, srcRef types.Im newImage.image = img newImages = append(newImages, &newImage) } - + ir.newImageEvent(events.LoadFromArchive, "") return newImages, nil } @@ -371,6 +373,7 @@ func (i *Image) Remove(force bool) error { } parent = nextParent } + defer i.newImageEvent(events.Remove) return nil } @@ -494,6 +497,7 @@ func (i *Image) TagImage(tag string) error { return err } i.reloadImage() + defer i.newImageEvent(events.Tag) return nil } @@ -514,6 +518,7 @@ func (i *Image) UntagImage(tag string) error { return err } i.reloadImage() + defer i.newImageEvent(events.Untag) return nil } @@ -562,6 +567,7 @@ func (i *Image) PushImageToReference(ctx context.Context, dest types.ImageRefere if err != nil { return errors.Wrapf(err, "Error copying image to the remote destination") } + defer i.newImageEvent(events.Push) return nil } @@ -711,7 +717,6 @@ func (i *Image) History(ctx context.Context) ([]*History, error) { Comment: oci.History[i].Comment, }) } - return allHistory, nil } @@ -927,7 +932,11 @@ func (ir *Runtime) Import(ctx context.Context, path, reference string, writer io if err != nil { return nil, err } - return ir.NewFromLocal(reference) + newImage, err := ir.NewFromLocal(reference) + if err == nil { + defer newImage.newImageEvent(events.Import) + } + return newImage, err } // MatchRepoTag takes a string and tries to match it against an @@ -1143,11 +1152,127 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag } } if err := i.PushImageToReference(ctx, destRef, manifestType, "", "", writer, compress, SigningOptions{}, &DockerRegistryOptions{}, additionaltags); err != nil { - if err2 := os.Remove(output); err2 != nil { - logrus.Errorf("error deleting %q: %v", output, err) - } return errors.Wrapf(err, "unable to save %q", source) } - + defer i.newImageEvent(events.Save) return nil } + +// GetConfigBlob returns a schema2image. If the image is not a schema2, then +// it will return an error +func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) { + imageRef, err := i.toImageRef(ctx) + if err != nil { + return nil, err + } + b, err := imageRef.ConfigBlob(ctx) + if err != nil { + return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID()) + } + blob := manifest.Schema2Image{} + if err := json.Unmarshal(b, &blob); err != nil { + return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID()) + } + return &blob, nil + +} + +// GetHealthCheck returns a HealthConfig for an image. This function only works with +// schema2 images. +func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) { + configBlob, err := i.GetConfigBlob(ctx) + if err != nil { + return nil, err + } + return configBlob.ContainerConfig.Healthcheck, nil +} + +// newImageEvent creates a new event based on an image +func (ir *Runtime) newImageEvent(status events.Status, name string) { + e := events.NewEvent(status) + e.Type = events.Image + e.Name = name + if err := e.Write(ir.EventsLogFilePath); err != nil { + logrus.Infof("unable to write event to %s", ir.EventsLogFilePath) + } +} + +// newImageEvent creates a new event based on an image +func (i *Image) newImageEvent(status events.Status) { + e := events.NewEvent(status) + e.ID = i.ID() + e.Type = events.Image + if len(i.Names()) > 0 { + e.Name = i.Names()[0] + } + if err := e.Write(i.imageruntime.EventsLogFilePath); err != nil { + logrus.Infof("unable to write event to %s", i.imageruntime.EventsLogFilePath) + } +} + +// LayerInfo keeps information of single layer +type LayerInfo struct { + // Layer ID + ID string + // Parent ID of current layer. + ParentID string + // ChildID of current layer. + // there can be multiple children in case of fork + ChildID []string + // RepoTag will have image repo names, if layer is top layer of image + RepoTags []string + // Size stores Uncompressed size of layer. + Size int64 +} + +// GetLayersMapWithImageInfo returns map of image-layers, with associated information like RepoTags, parent and list of child layers. +func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, error) { + + // Memory allocated to store map of layers with key LayerID. + // Map will build dependency chain with ParentID and ChildID(s) + layerInfoMap := make(map[string]*LayerInfo) + + // scan all layers & fill size and parent id for each layer in layerInfoMap + layers, err := imageruntime.store.Layers() + if err != nil { + return nil, err + } + for _, layer := range layers { + _, ok := layerInfoMap[layer.ID] + if !ok { + layerInfoMap[layer.ID] = &LayerInfo{ + ID: layer.ID, + Size: layer.UncompressedSize, + ParentID: layer.Parent, + } + } else { + return nil, fmt.Errorf("detected multiple layers with the same ID %q", layer.ID) + } + } + + // scan all layers & add all childs for each layers to layerInfo + for _, layer := range layers { + _, ok := layerInfoMap[layer.ID] + if ok { + if layer.Parent != "" { + layerInfoMap[layer.Parent].ChildID = append(layerInfoMap[layer.Parent].ChildID, layer.ID) + } + } else { + return nil, fmt.Errorf("lookup error: layer-id %s, not found", layer.ID) + } + } + + // Add the Repo Tags to Top layer of each image. + imgs, err := imageruntime.store.Images() + if err != nil { + return nil, err + } + for _, img := range imgs { + e, ok := layerInfoMap[img.TopLayer] + if !ok { + return nil, fmt.Errorf("top-layer for image %s not found local store", img.ID) + } + e.RepoTags = append(e.RepoTags, img.Names...) + } + return layerInfoMap, nil +} diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 8602c222c..5bd3c2c99 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -1,6 +1,9 @@ package image -import "github.com/pkg/errors" +import ( + "github.com/containers/libpod/libpod/events" + "github.com/pkg/errors" +) // GetPruneImages returns a slice of images that have no names/unused func (ir *Runtime) GetPruneImages(all bool) ([]*Image, error) { @@ -41,6 +44,7 @@ func (ir *Runtime) PruneImages(all bool) ([]string, error) { if err := p.Remove(true); err != nil { return nil, errors.Wrap(err, "failed to prune image") } + defer p.newImageEvent(events.Prune) prunedCids = append(prunedCids, p.ID()) } return prunedCids, nil diff --git a/libpod/image/pull.go b/libpod/image/pull.go index 6b9f7fc67..a3b716e65 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -17,6 +17,7 @@ import ( "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/registries" multierror "github.com/hashicorp/go-multierror" opentracing "github.com/opentracing/opentracing-go" @@ -267,12 +268,13 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa _, err = cp.Image(ctx, policyContext, imageInfo.dstRef, imageInfo.srcRef, copyOptions) if err != nil { pullErrors = multierror.Append(pullErrors, err) - logrus.Debugf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) + logrus.Errorf("Error pulling image ref %s: %v", imageInfo.srcRef.StringWithinTransport(), err) if writer != nil { io.WriteString(writer, "Failed\n") } } else { if !goal.pullAllPairs { + ir.newImageEvent(events.Pull, "") return []string{imageInfo.image}, nil } images = append(images, imageInfo.image) @@ -293,6 +295,9 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa } return nil, pullErrors } + if len(images) > 0 { + defer ir.newImageEvent(events.Pull, images[0]) + } return images, nil } diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index f9caf26d1..2450bd6b1 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -134,15 +134,29 @@ type slirp4netnsCmd struct { Args slirp4netnsCmdArg `json:"arguments"` } +func checkSlirpFlags(path string) (bool, bool, error) { + cmd := exec.Command(path, "--help") + out, err := cmd.CombinedOutput() + if err != nil { + return false, false, err + } + return strings.Contains(string(out), "--disable-host-loopback"), strings.Contains(string(out), "--mtu"), nil +} + // Configure the network namespace for a rootless container func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { defer ctr.rootlessSlirpSyncR.Close() defer ctr.rootlessSlirpSyncW.Close() - path, err := exec.LookPath("slirp4netns") - if err != nil { - logrus.Errorf("could not find slirp4netns, the network namespace won't be configured: %v", err) - return nil + path := r.config.NetworkCmdPath + + if path == "" { + var err error + path, err = exec.LookPath("slirp4netns") + if err != nil { + logrus.Errorf("could not find slirp4netns, the network namespace won't be configured: %v", err) + return nil + } } syncR, syncW, err := os.Pipe() @@ -154,13 +168,24 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { havePortMapping := len(ctr.Config().PortMappings) > 0 apiSocket := filepath.Join(r.ociRuntime.tmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) - var cmd *exec.Cmd + + cmdArgs := []string{} if havePortMapping { - // if we need ports to be mapped from the host, create a API socket to use for communicating with slirp4netns. - cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID), "tap0") - } else { - cmd = exec.Command(path, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") + cmdArgs = append(cmdArgs, "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID)) + } + dhp, mtu, err := checkSlirpFlags(path) + if err != nil { + return errors.Wrapf(err, "error checking slirp4netns binary %s", path) + } + if dhp { + cmdArgs = append(cmdArgs, "--disable-host-loopback") } + if mtu { + cmdArgs = append(cmdArgs, "--mtu", "65520") + } + cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") + + cmd := exec.Command(path, cmdArgs...) cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, @@ -190,9 +215,12 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { if pid != cmd.Process.Pid { continue } - if status.Exited() || status.Signaled() { + if status.Exited() { return errors.New("slirp4netns failed") } + if status.Signaled() { + return errors.New("slirp4netns killed by signal") + } continue } return errors.Wrapf(err, "failed to read from slirp4netns sync pipe") diff --git a/libpod/oci.go b/libpod/oci.go index 4bf76f619..69cff6d3c 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -183,6 +183,7 @@ func waitPidsStop(pids []int, timeout time.Duration) error { func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { var files []*os.File + notifySCTP := false for _, i := range ports { switch i.Protocol { case "udp": @@ -218,6 +219,12 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { } files = append(files, f) break + case "sctp": + if !notifySCTP { + notifySCTP = true + logrus.Warnf("port reservation for SCTP is not supported") + } + break default: return nil, fmt.Errorf("unknown protocol %s", i.Protocol) @@ -477,7 +484,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRunc bool) error { // If not using runc, we don't need to do most of this. if !useRunc { // If the container's not running, nothing to do. - if ctr.state.State != ContainerStateRunning { + if ctr.state.State != ContainerStateRunning && ctr.state.State != ContainerStatePaused { return nil } @@ -733,7 +740,7 @@ func (r *OCIRuntime) unpauseContainer(ctr *Container) error { // TODO: Add --detach support // TODO: Convert to use conmon // TODO: add --pid-file and use that to generate exec session tracking -func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string, streams *AttachStreams) (*exec.Cmd, error) { +func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string, streams *AttachStreams, preserveFDs int) (*exec.Cmd, error) { if len(cmd) == 0 { return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute") } @@ -770,6 +777,9 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty args = append(args, "--user", user) } + if preserveFDs > 0 { + args = append(args, fmt.Sprintf("--preserve-fds=%d", preserveFDs)) + } if c.config.Spec.Process.NoNewPrivileges { args = append(args, "--no-new-privs") } @@ -802,10 +812,24 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + if preserveFDs > 0 { + for fd := 3; fd < 3+preserveFDs; fd++ { + execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) + } + } + if err := execCmd.Start(); err != nil { return nil, errors.Wrapf(err, "cannot start container %s", c.ID()) } + 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() + } + } + return execCmd, nil } @@ -898,3 +922,16 @@ func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckp args = append(args, ctr.ID()) return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } + +func (r *OCIRuntime) featureCheckCheckpointing() bool { + // Check if the runtime implements checkpointing. Currently only + // runc's checkpoint/restore implementation is supported. + cmd := exec.Command(r.path, "checkpoint", "-h") + if err := cmd.Start(); err != nil { + return false + } + if err := cmd.Wait(); err == nil { + return true + } + return false +} diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 2737a641e..f85c5ee62 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -106,6 +106,23 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restor if err != nil { return } + + if ctr.state.UserNSRoot != "" { + _, err := os.Stat(ctr.runtime.config.VolumePath) + if err != nil && !os.IsNotExist(err) { + return + } + if err == nil { + volumesTarget := filepath.Join(ctr.state.UserNSRoot, "volumes") + if err := idtools.MkdirAs(volumesTarget, 0700, ctr.RootUID(), ctr.RootGID()); err != nil { + return + } + if err = unix.Mount(ctr.runtime.config.VolumePath, volumesTarget, "none", unix.MS_BIND, ""); err != nil { + return + } + } + } + err = r.createOCIContainer(ctr, cgroupParent, restoreOptions) }() wg.Wait() diff --git a/libpod/options.go b/libpod/options.go index 1e8592a25..1bf3ff9e6 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -7,6 +7,7 @@ import ( "regexp" "syscall" + "github.com/containers/image/manifest" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" @@ -192,6 +193,20 @@ func WithConmonEnv(environment []string) RuntimeOption { } } +// WithNetworkCmdPath specifies the path to the slirp4netns binary which manages the +// runtime. +func WithNetworkCmdPath(path string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + rt.config.NetworkCmdPath = path + + return nil + } +} + // WithCgroupManager specifies the manager implementation name which is used to // handle cgroups for containers. // Current valid values are "cgroupfs" and "systemd". @@ -271,7 +286,6 @@ func WithTmpDir(dir string) RuntimeOption { if rt.valid { return ErrRuntimeFinalized } - rt.config.TmpDir = dir rt.configuredFrom.libpodTmpDirSet = true @@ -1469,3 +1483,14 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption { return nil } } + +// WithHealthCheck adds the healthcheck to the container config +func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.HealthCheckConfig = healthCheck + return nil + } +} diff --git a/libpod/pod_api.go b/libpod/pod_api.go index cbac2420f..b9a11000e 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -3,6 +3,7 @@ package libpod import ( "context" + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" @@ -58,7 +59,7 @@ func (p *Pod) Start(ctx context.Context) (map[string]error, error) { if len(ctrErrors) > 0 { return ctrErrors, errors.Wrapf(ErrCtrExists, "error starting some containers") } - + defer p.newPodEvent(events.Start) return nil, nil } @@ -138,7 +139,7 @@ func (p *Pod) StopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m if len(ctrErrors) > 0 { return ctrErrors, errors.Wrapf(ErrCtrExists, "error stopping some containers") } - + defer p.newPodEvent(events.Stop) return nil, nil } @@ -197,7 +198,7 @@ func (p *Pod) Pause() (map[string]error, error) { if len(ctrErrors) > 0 { return ctrErrors, errors.Wrapf(ErrCtrExists, "error pausing some containers") } - + defer p.newPodEvent(events.Pause) return nil, nil } @@ -257,6 +258,7 @@ func (p *Pod) Unpause() (map[string]error, error) { return ctrErrors, errors.Wrapf(ErrCtrExists, "error unpausing some containers") } + defer p.newPodEvent(events.Unpause) return nil, nil } @@ -309,7 +311,8 @@ func (p *Pod) Restart(ctx context.Context) (map[string]error, error) { if len(ctrErrors) > 0 { return ctrErrors, errors.Wrapf(ErrCtrExists, "error stopping some containers") } - + p.newPodEvent(events.Stop) + p.newPodEvent(events.Start) return nil, nil } @@ -367,7 +370,7 @@ func (p *Pod) Kill(signal uint) (map[string]error, error) { if len(ctrErrors) > 0 { return ctrErrors, errors.Wrapf(ErrCtrExists, "error killing some containers") } - + defer p.newPodEvent(events.Kill) return nil, nil } diff --git a/libpod/runtime.go b/libpod/runtime.go index f53cdd8b8..b3b75d791 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -217,10 +217,15 @@ type RuntimeConfig struct { EnablePortReservation bool `toml:"enable_port_reservation"` // EnableLabeling indicates wether libpod will support container labeling EnableLabeling bool `toml:"label"` + // NetworkCmdPath is the path to the slirp4netns binary + NetworkCmdPath string `toml:"network_cmd_path"` // NumLocks is the number of locks to make available for containers and // pods. NumLocks uint32 `toml:"num_locks,omitempty"` + + // EventsLogFilePath is where the events log is stored. + EventsLogFilePath string `toml:-"events_logfile_path"` } // runtimeConfiguredFrom is a struct used during early runtime init to help @@ -236,6 +241,12 @@ type runtimeConfiguredFrom struct { libpodStaticDirSet bool libpodTmpDirSet bool volPathSet bool + conmonPath bool + conmonEnvVars bool + ociRuntimes bool + runtimePath bool + cniPluginDir bool + noPivotRoot bool } var ( @@ -319,6 +330,22 @@ func SetXdgRuntimeDir(val string) error { // NewRuntime creates a new container runtime // Options can be passed to override the default configuration for the runtime func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { + return newRuntimeFromConfig("", options...) +} + +// NewRuntimeFromConfig creates a new container runtime using the given +// configuration file for its default configuration. Passed RuntimeOption +// functions can be used to mutate this configuration further. +// An error will be returned if the configuration file at the given path does +// not exist or cannot be loaded +func NewRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) { + if userConfigPath == "" { + return nil, errors.New("invalid configuration file specified") + } + return newRuntimeFromConfig(userConfigPath, options...) +} + +func newRuntimeFromConfig(userConfigPath string, options ...RuntimeOption) (runtime *Runtime, err error) { runtime = new(Runtime) runtime.config = new(RuntimeConfig) runtime.configuredFrom = new(runtimeConfiguredFrom) @@ -333,7 +360,7 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { storageConf, err := util.GetDefaultStoreOptions() if err != nil { - return nil, errors.Wrapf(err, "error retrieving rootless storage config") + return nil, errors.Wrapf(err, "error retrieving storage config") } runtime.config.StorageConfig = storageConf runtime.config.StaticDir = filepath.Join(storageConf.GraphRoot, "libpod") @@ -353,11 +380,6 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { rootlessConfigPath = filepath.Join(home, ".config/containers/libpod.conf") - configPath = rootlessConfigPath - if _, err := os.Stat(configPath); err != nil { - foundConfig = false - } - runtimeDir, err := util.GetRootlessRuntimeDir() if err != nil { return nil, err @@ -365,11 +387,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { // containers/image uses XDG_RUNTIME_DIR to locate the auth file. // So make sure the env variable is set. - err = SetXdgRuntimeDir(runtimeDir) - if err != nil { + if err := SetXdgRuntimeDir(runtimeDir); err != nil { return nil, errors.Wrapf(err, "cannot set XDG_RUNTIME_DIR") } + } + + if userConfigPath != "" { + configPath = userConfigPath + if _, err := os.Stat(configPath); err != nil { + // If the user specified a config file, we must fail immediately + // when it doesn't exist + return nil, errors.Wrapf(err, "cannot stat %s", configPath) + } + } else if rootless.IsRootless() { + configPath = rootlessConfigPath + if _, err := os.Stat(configPath); err != nil { + foundConfig = false + } } else if _, err := os.Stat(OverrideConfigPath); err == nil { // Use the override configuration path configPath = OverrideConfigPath @@ -405,6 +440,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if tmpConfig.VolumePath != "" { runtime.configuredFrom.volPathSet = true } + if tmpConfig.ConmonPath != nil { + runtime.configuredFrom.conmonPath = true + } + if tmpConfig.ConmonEnvVars != nil { + runtime.configuredFrom.conmonEnvVars = true + } + if tmpConfig.OCIRuntimes != nil { + runtime.configuredFrom.ociRuntimes = true + } + if tmpConfig.RuntimePath != nil { + runtime.configuredFrom.runtimePath = true + } + if tmpConfig.CNIPluginDir != nil { + runtime.configuredFrom.cniPluginDir = true + } + if tmpConfig.NoPivotRoot { + runtime.configuredFrom.noPivotRoot = true + } if _, err := toml.Decode(string(contents), runtime.config); err != nil { return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath) @@ -424,12 +477,24 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { } // Cherry pick the settings we want from the global configuration - runtime.config.ConmonPath = tmpConfig.ConmonPath - runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars - runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes - runtime.config.RuntimePath = tmpConfig.RuntimePath - runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir - runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot + if !runtime.configuredFrom.conmonPath { + runtime.config.ConmonPath = tmpConfig.ConmonPath + } + if !runtime.configuredFrom.conmonEnvVars { + runtime.config.ConmonEnvVars = tmpConfig.ConmonEnvVars + } + if !runtime.configuredFrom.ociRuntimes { + runtime.config.OCIRuntimes = tmpConfig.OCIRuntimes + } + if !runtime.configuredFrom.runtimePath { + runtime.config.RuntimePath = tmpConfig.RuntimePath + } + if !runtime.configuredFrom.cniPluginDir { + runtime.config.CNIPluginDir = tmpConfig.CNIPluginDir + } + if !runtime.configuredFrom.noPivotRoot { + runtime.config.NoPivotRoot = tmpConfig.NoPivotRoot + } break } } @@ -440,77 +505,39 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { return nil, errors.Wrapf(err, "error configuring runtime") } } - if err := makeRuntime(runtime); err != nil { - return nil, err - } - - if !foundConfig && rootlessConfigPath != "" { - os.MkdirAll(filepath.Dir(rootlessConfigPath), 0755) - file, err := os.OpenFile(rootlessConfigPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) - if err != nil && !os.IsExist(err) { - return nil, errors.Wrapf(err, "cannot open file %s", rootlessConfigPath) - } - if err == nil { - defer file.Close() - enc := toml.NewEncoder(file) - if err := enc.Encode(runtime.config); err != nil { - os.Remove(rootlessConfigPath) + if rootlessConfigPath != "" { + // storage.conf + storageConfFile := util.StorageConfigFile() + if _, err := os.Stat(storageConfFile); os.IsNotExist(err) { + if err := util.WriteStorageConfigFile(&runtime.config.StorageConfig, storageConfFile); err != nil { + return nil, errors.Wrapf(err, "cannot write config file %s", storageConfFile) } } - } - - return runtime, nil -} -// NewRuntimeFromConfig creates a new container runtime using the given -// configuration file for its default configuration. Passed RuntimeOption -// functions can be used to mutate this configuration further. -// An error will be returned if the configuration file at the given path does -// not exist or cannot be loaded -func NewRuntimeFromConfig(configPath string, options ...RuntimeOption) (runtime *Runtime, err error) { - runtime = new(Runtime) - runtime.config = new(RuntimeConfig) - runtime.configuredFrom = new(runtimeConfiguredFrom) - - // Set three fields not in the TOML config - runtime.config.StateType = defaultRuntimeConfig.StateType - runtime.config.OCIRuntime = defaultRuntimeConfig.OCIRuntime - runtime.config.StorageConfig = storage.StoreOptions{} - - // Check to see if the given configuration file exists - if _, err := os.Stat(configPath); err != nil { - return nil, errors.Wrapf(err, "error checking existence of configuration file %s", configPath) - } - - // Read contents of the config file - contents, err := ioutil.ReadFile(configPath) - if err != nil { - return nil, errors.Wrapf(err, "error reading configuration file %s", configPath) - } - - // Decode configuration file - if _, err := toml.Decode(string(contents), runtime.config); err != nil { - return nil, errors.Wrapf(err, "error decoding configuration from file %s", configPath) - } - - // Overwrite the config with user-given configuration options - for _, opt := range options { - if err := opt(runtime); err != nil { - return nil, errors.Wrapf(err, "error configuring runtime") + if !foundConfig { + os.MkdirAll(filepath.Dir(rootlessConfigPath), 0755) + file, err := os.OpenFile(rootlessConfigPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if err != nil && !os.IsExist(err) { + return nil, errors.Wrapf(err, "cannot open file %s", rootlessConfigPath) + } + if err == nil { + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(runtime.config); err != nil { + os.Remove(rootlessConfigPath) + } + } } } - if err := makeRuntime(runtime); err != nil { return nil, err } - return runtime, nil } // Make a new runtime based on the given configuration // Sets up containers/storage, state store, OCI runtime func makeRuntime(runtime *Runtime) (err error) { - // Backward compatibility for `runtime_path` if runtime.config.RuntimePath != nil { // Don't print twice in rootless mode. @@ -669,6 +696,8 @@ func makeRuntime(runtime *Runtime) (err error) { runtime.config.VolumePath = dbConfig.VolumePath } + runtime.config.EventsLogFilePath = filepath.Join(runtime.config.TmpDir, "events", "events.log") + logrus.Debugf("Using graph driver %s", runtime.config.StorageConfig.GraphDriverName) logrus.Debugf("Using graph root %s", runtime.config.StorageConfig.GraphRoot) logrus.Debugf("Using run root %s", runtime.config.StorageConfig.RunRoot) @@ -711,6 +740,9 @@ func makeRuntime(runtime *Runtime) (err error) { // Setting signaturepolicypath ir.SignaturePolicyPath = runtime.config.SignaturePolicyPath + // Set logfile path for events + ir.EventsLogFilePath = runtime.config.EventsLogFilePath + defer func() { if err != nil && store != nil { // Don't forcibly shut down @@ -743,6 +775,14 @@ func makeRuntime(runtime *Runtime) (err error) { } } + // Create events log dir + if err := os.MkdirAll(filepath.Dir(runtime.config.EventsLogFilePath), 0700); err != nil { + // The directory is allowed to exist + if !os.IsExist(err) { + return errors.Wrapf(err, "error creating events dirs %s", filepath.Dir(runtime.config.EventsLogFilePath)) + } + } + // Make an OCI runtime to perform container operations ociRuntime, err := newOCIRuntime(runtime.ociRuntimePath, runtime.conmonPath, runtime.config.ConmonEnvVars, @@ -948,9 +988,12 @@ 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") - err := cmd.Run() - if err != nil { - return errors.Wrapf(err, "Error running %s info while refreshing state", os.Args[0]) + + 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 } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index cfa4f9654..f23dc86dd 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -170,7 +171,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. }() if rootless.IsRootless() && ctr.config.ConmonPidFile == "" { - ctr.config.ConmonPidFile = filepath.Join(ctr.state.RunDir, "conmon.pid") + ctr.config.ConmonPidFile = filepath.Join(ctr.config.StaticDir, "conmon.pid") } // Go through the volume mounts and check for named volumes @@ -185,8 +186,11 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) } ctr.config.Spec.Mounts[i].Source = newVol.MountPoint() + if err := os.Chown(ctr.config.Spec.Mounts[i].Source, ctr.RootUID(), ctr.RootGID()); err != nil { + return nil, errors.Wrapf(err, "cannot chown %q to %d:%d", ctr.config.Spec.Mounts[i].Source, ctr.RootUID(), ctr.RootGID()) + } 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) + return nil, errors.Wrapf(err, "failed to copy content into new volume mount %q", vol.Source) } continue } @@ -228,6 +232,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. return nil, err } } + ctr.newContainerEvent(events.Create) return ctr, nil } @@ -239,7 +244,6 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. func (r *Runtime) RemoveContainer(ctx context.Context, c *Container, force bool, removeVolume bool) error { r.lock.Lock() defer r.lock.Unlock() - return r.removeContainer(ctx, c, force, removeVolume) } @@ -430,6 +434,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } + c.newContainerEvent(events.Remove) return cleanupErr } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 451c2ebe7..02f925fc6 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -14,6 +14,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/containers/image/directory" dockerarchive "github.com/containers/image/docker/archive" @@ -183,6 +184,15 @@ func (r *Runtime) Import(ctx context.Context, source string, reference string, c defer os.Remove(file) source = file } + // if it's stdin, buffer it, too + if source == "-" { + file, err := downloadFromFile(os.Stdin) + if err != nil { + return "", err + } + defer os.Remove(file) + source = file + } newImage, err := r.imageRuntime.Import(ctx, source, reference, writer, image.SigningOptions{}, config) if err != nil { @@ -216,6 +226,25 @@ func downloadFromURL(source string) (string, error) { return outFile.Name(), nil } +// donwloadFromFile reads all of the content from the reader and temporarily +// saves in it /var/tmp/importxyz, which is deleted after the image is imported +func downloadFromFile(reader *os.File) (string, error) { + outFile, err := ioutil.TempFile("/var/tmp", "import") + if err != nil { + return "", errors.Wrap(err, "error creating file") + } + defer outFile.Close() + + logrus.Debugf("saving %s to %s", reader.Name(), outFile.Name()) + + _, err = io.Copy(outFile, reader) + if err != nil { + return "", errors.Wrapf(err, "error saving %s to %s", reader.Name(), outFile.Name()) + } + + return outFile.Name(), nil +} + // LoadImage loads a container image into local storage func (r *Runtime) LoadImage(ctx context.Context, name, inputFile string, writer io.Writer, signaturePolicy string) (string, error) { var newImages []*image.Image diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index 4f221764a..0a5f78cf8 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -4,11 +4,15 @@ package libpod import ( "context" + "strings" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/opencontainers/image-spec/specs-go/v1" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) const ( @@ -17,7 +21,7 @@ const ( IDTruncLength = 12 ) -func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID string) (*Container, error) { +func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID string, config *v1.ImageConfig) (*Container, error) { // Set up generator for infra container defaults g, err := generate.New("linux") @@ -27,8 +31,44 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID isRootless := rootless.IsRootless() + entryCmd := []string{r.config.InfraCommand} + // I've seen circumstances where config is being passed as nil. + // Let's err on the side of safety and make sure it's safe to use. + if config != nil { + setEntrypoint := false + // default to entrypoint in image if there is one + if len(config.Entrypoint) > 0 { + entryCmd = config.Entrypoint + setEntrypoint = true + } + if len(config.Cmd) > 0 { + // We can't use the default pause command, since we're + // sourcing from the image. If we didn't already set an + // entrypoint, set one now. + if !setEntrypoint { + // Use the Docker default "/bin/sh -c" + // entrypoint, as we're overriding command. + // If an image doesn't want this, it can + // override entrypoint too. + entryCmd = []string{"/bin/sh", "-c"} + } + entryCmd = append(entryCmd, config.Cmd...) + } + if len(config.Env) > 0 { + for _, nameValPair := range config.Env { + nameValSlice := strings.Split(nameValPair, "=") + if len(nameValSlice) < 2 { + return nil, errors.Errorf("Invalid environment variable structure in pause image") + } + g.AddProcessEnv(nameValSlice[0], nameValSlice[1]) + } + } + } + g.SetRootReadonly(true) - g.SetProcessArgs([]string{r.config.InfraCommand}) + g.SetProcessArgs(entryCmd) + + logrus.Debugf("Using %q as infra container entrypoint", entryCmd) if isRootless { g.RemoveMount("/dev/pts") @@ -79,5 +119,5 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container, imageName := newImage.Names()[0] imageID := data.ID - return r.makeInfraContainer(ctx, p, imageName, imageID) + return r.makeInfraContainer(ctx, p, imageName, imageID, data.Config) } diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index c378d18e4..0011c771a 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/containerd/cgroups" + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -95,9 +96,12 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod, if pod.config.UsePodCgroup { logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath) } - if pod.HasInfraContainer() != pod.SharesNamespaces() { + if !pod.HasInfraContainer() && pod.SharesNamespaces() { return nil, errors.Errorf("Pods must have an infra container to share namespaces") } + if pod.HasInfraContainer() && !pod.SharesNamespaces() { + logrus.Warnf("Pod has an infra container, but shares no namespaces") + } if err := r.state.AddPod(pod); err != nil { return nil, errors.Wrapf(err, "error adding pod to state") @@ -118,7 +122,7 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod, return nil, err } } - + pod.newPodEvent(events.Create) return pod, nil } @@ -304,6 +308,6 @@ func (r *Runtime) removePod(ctx context.Context, p *Pod, removeCtrs, force bool) // Mark pod invalid p.valid = false - + p.newPodEvent(events.Remove) return nil } diff --git a/libpod/runtime_volume.go b/libpod/runtime_volume.go index 11f37ad4b..68c6c107e 100644 --- a/libpod/runtime_volume.go +++ b/libpod/runtime_volume.go @@ -2,9 +2,11 @@ package libpod import ( "context" + "strings" + + "github.com/containers/libpod/libpod/events" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "strings" ) // Contains the public Runtime API for volumes @@ -34,7 +36,6 @@ func (r *Runtime) RemoveVolume(ctx context.Context, v *Volume, force bool) error return nil } } - return r.removeVolume(ctx, v, force) } @@ -171,6 +172,7 @@ func (r *Runtime) PruneVolumes(ctx context.Context) ([]string, []error) { } continue } + vol.newVolumeEvent(events.Prune) prunedIDs = append(prunedIDs, vol.Name()) } return prunedIDs, pruneErrors diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go index 838c0167a..b51bb8213 100644 --- a/libpod/runtime_volume_linux.go +++ b/libpod/runtime_volume_linux.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/containers/libpod/libpod/events" "github.com/containers/storage/pkg/stringid" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" @@ -73,7 +74,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption) if err := r.state.AddVolume(volume); err != nil { return nil, errors.Wrapf(err, "error adding volume to state") } - + defer volume.newVolumeEvent(events.Create) return volume, nil } @@ -118,7 +119,7 @@ func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error return errors.Wrapf(err, "error cleaning up volume storage for %q", v.Name()) } + defer v.newVolumeEvent(events.Remove) logrus.Debugf("Removed volume %s", v.Name()) - return nil } diff --git a/libpod/runtime_volume_unsupported.go b/libpod/runtime_volume_unsupported.go index d87459759..5fe487114 100644 --- a/libpod/runtime_volume_unsupported.go +++ b/libpod/runtime_volume_unsupported.go @@ -6,7 +6,7 @@ import ( "context" ) -func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force, prune bool) error { +func (r *Runtime) removeVolume(ctx context.Context, v *Volume, force bool) error { return ErrNotImplemented } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go new file mode 100644 index 000000000..932d209cd --- /dev/null +++ b/pkg/adapter/containers.go @@ -0,0 +1,156 @@ +// +build !remoteclient + +package adapter + +import ( + "context" + "fmt" + "strconv" + "sync" + "syscall" + "time" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// GetLatestContainer gets the latest Container and wraps it in an adapter Container +func (r *LocalRuntime) GetLatestContainer() (*Container, error) { + Container := Container{} + c, err := r.Runtime.GetLatestContainer() + Container.Container = c + return &Container, err +} + +// GetAllContainers gets all Containers and wraps each one in an adapter Container +func (r *LocalRuntime) GetAllContainers() ([]*Container, error) { + var containers []*Container + allContainers, err := r.Runtime.GetAllContainers() + if err != nil { + return nil, err + } + + for _, c := range allContainers { + containers = append(containers, &Container{c}) + } + return containers, nil +} + +// LookupContainer gets a Container by name or id and wraps it in an adapter Container +func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { + ctr, err := r.Runtime.LookupContainer(idOrName) + if err != nil { + return nil, err + } + return &Container{ctr}, nil +} + +// StopContainers stops container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) + error, or error not from container +func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) { + var timeout *uint + if cli.Flags().Changed("timeout") || cli.Flags().Changed("time") { + t := uint(cli.Timeout) + timeout = &t + } + + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, c := range ctrs { + if timeout == nil { + t := c.StopTimeout() + timeout = &t + logrus.Debugf("Set timeout to container %s default (%d)", c.ID(), *timeout) + } + if err := c.StopWithTimeout(*timeout); err == nil { + ok = append(ok, c.ID()) + } else if errors.Cause(err) == libpod.ErrCtrStopped { + ok = append(ok, c.ID()) + logrus.Debugf("Container %s is already stopped", c.ID()) + } else { + failures[c.ID()] = err + } + } + return ok, failures, nil +} + +// KillContainers sends signal to container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) + error, or error not from container +func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, c := range ctrs { + if err := c.Kill(uint(signal)); err == nil { + ok = append(ok, c.ID()) + } else { + failures[c.ID()] = err + } + } + return ok, failures, nil +} + +// WaitOnContainers waits for all given container(s) to stop +func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(false, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, c := range ctrs { + if returnCode, err := c.WaitWithInterval(interval); err == nil { + ok = append(ok, strconv.Itoa(int(returnCode))) + } else { + failures[c.ID()] = err + } + } + return ok, failures, err +} + +// Log logs one or more containers +func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error { + var wg sync.WaitGroup + options.WaitGroup = &wg + if len(c.InputArgs) > 1 { + options.Multi = true + } + logChannel := make(chan *libpod.LogLine, int(c.Tail)*len(c.InputArgs)+1) + containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime) + if err != nil { + return err + } + if err := r.Runtime.Log(containers, options, logChannel); err != nil { + return err + } + go func() { + wg.Wait() + close(logChannel) + }() + for line := range logChannel { + fmt.Println(line.String(options)) + } + return nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 3f43a6905..2982d6cbb 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -3,17 +3,26 @@ package adapter import ( + "context" "encoding/json" - "github.com/containers/libpod/cmd/podman/shared" + "fmt" + "strconv" + "syscall" + "time" - iopodman "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/varlink/go/varlink" ) // Inspect returns an inspect struct from varlink func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) { - reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID()) + reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size) if err != nil { return nil, err } @@ -29,6 +38,70 @@ func (c *Container) ID() string { return c.config.ID } +// Config returns a container config +func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { + // TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer + // further looking into it for after devconf. + // The libpod function for this has no errors so we are kind of in a tough + // spot here. Logging the errors for now. + reply, err := iopodman.ContainerConfig().Call(r.Conn, name) + if err != nil { + logrus.Error("call to container.config failed") + } + data := libpod.ContainerConfig{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + logrus.Error("failed to unmarshal container inspect data") + } + return &data + +} + +// ContainerState returns the "state" of the container. +func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, error) { // no-lint + reply, err := iopodman.ContainerStateData().Call(r.Conn, name) + if err != nil { + return nil, err + } + data := libpod.ContainerState{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, err + +} + +// LookupContainer gets basic information about container over a varlink +// connection and then translates it to a *Container +func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { + state, err := r.ContainerState(idOrName) + if err != nil { + return nil, err + } + config := r.Config(idOrName) + if err != nil { + return nil, err + } + + return &Container{ + remoteContainer{ + r, + config, + state, + }, + }, nil +} + +func (r *LocalRuntime) GetLatestContainer() (*Container, error) { + reply, err := iopodman.GetContainersByContext().Call(r.Conn, false, true, nil) + if err != nil { + return nil, err + } + if len(reply) > 0 { + return r.LookupContainer(reply[0]) + } + return nil, errors.New("no containers exist") +} + // GetArtifact returns a container's artifacts func (c *Container) GetArtifact(name string) ([]byte, error) { var data []byte @@ -55,18 +128,90 @@ func (c *Container) Name() string { return c.config.Name } +// StopContainers stops requested containers using CLI inputs. +// Returns the list of stopped container ids, map of failed to stop container ids + errors, or any non-container error +func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return ok, failures, err + } + + for _, id := range ids { + stopped, err := iopodman.StopContainer().Call(r.Conn, id, int64(cli.Timeout)) + if err != nil { + failures[id] = err + } else { + ok = append(ok, stopped) + } + } + return ok, failures, nil +} + +// KillContainers sends signal to container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) + error, or error not from container +func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return ok, failures, err + } + + for _, id := range ids { + killed, err := iopodman.KillContainer().Call(r.Conn, id, int64(signal)) + if err != nil { + failures[id] = err + } else { + ok = append(ok, killed) + } + } + return ok, failures, nil +} + +// WaitOnContainers waits for all given container(s) to stop. +// interval is currently ignored. +func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, false, cli.Latest, cli.InputArgs) + if err != nil { + return ok, failures, err + } + + for _, id := range ids { + stopped, err := iopodman.WaitContainer().Call(r.Conn, id, int64(interval)) + if err != nil { + failures[id] = err + } else { + ok = append(ok, strconv.FormatInt(stopped, 10)) + } + } + return ok, failures, nil +} + // BatchContainerOp is wrapper func to mimic shared's function with a similar name meant for libpod func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContainerStruct, error) { // TODO If pod ps ever shows container's sizes, re-enable this code; otherwise it isn't needed // and would be a perf hit - //data, err := ctr.Inspect(true) - //if err != nil { - // return shared.BatchContainerStruct{}, err - //} + // data, err := ctr.Inspect(true) + // if err != nil { + // return shared.BatchContainerStruct{}, err + // } // - //size := new(shared.ContainerSize) - //size.RootFsSize = data.SizeRootFs - //size.RwSize = data.SizeRw + // size := new(shared.ContainerSize) + // size.RootFsSize = data.SizeRootFs + // size.RwSize = data.SizeRw bcs := shared.BatchContainerStruct{ ConConfig: ctr.config, @@ -75,7 +220,45 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai Pid: ctr.state.PID, StartedTime: ctr.state.StartedTime, ExitedTime: ctr.state.FinishedTime, - //Size: size, + // Size: size, } return bcs, nil } + +// Logs one or more containers over a varlink connection +func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error { + //GetContainersLogs + reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps) + if err != nil { + return errors.Wrapf(err, "failed to get container logs") + } + if len(c.InputArgs) > 1 { + options.Multi = true + } + for { + log, flags, err := reply() + if err != nil { + return err + } + if log.Time == "" && log.Msg == "" { + // We got a blank log line which can signal end of stream + break + } + lTime, err := time.Parse(time.RFC3339Nano, log.Time) + if err != nil { + return errors.Wrapf(err, "unable to parse time of log %s", log.Time) + } + logLine := libpod.LogLine{ + Device: log.Device, + ParseLogType: log.ParseLogType, + Time: lTime, + Msg: log.Msg, + CID: log.Cid, + } + fmt.Println(logLine.String(options)) + if flags&varlink.Continues == 0 { + break + } + } + return nil +} diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 706a8fe96..669971789 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -4,6 +4,7 @@ package adapter import ( "context" + "github.com/pkg/errors" "strings" "github.com/containers/libpod/cmd/podman/cliconfig" @@ -17,6 +18,13 @@ type Pod struct { *libpod.Pod } +// PodContainerStats is struct containing an adapter Pod and a libpod +// ContainerStats and is used primarily for outputing pod stats. +type PodContainerStats struct { + Pod *Pod + ContainerStats map[string]*libpod.ContainerStats +} + // RemovePods ... func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValues) ([]string, []error) { var ( @@ -321,3 +329,55 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV return restartIDs, containerErrors, restartErrors } + +// PodTop is a wrapper function to call GetPodPidInformation in libpod and return its results +// for output +func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) { + var ( + pod *Pod + err error + ) + + if c.Latest { + pod, err = r.GetLatestPod() + } else { + pod, err = r.LookupPod(c.InputArgs[0]) + } + if err != nil { + return nil, errors.Wrapf(err, "unable to lookup requested container") + } + podStatus, err := pod.GetPodStatus() + if err != nil { + return nil, errors.Wrapf(err, "unable to get status for pod %s", pod.ID()) + } + if podStatus != "Running" { + return nil, errors.Errorf("pod top can only be used on pods with at least one running container") + } + return pod.GetPodPidInformation(descriptors) +} + +// GetStatPods returns pods for use in pod stats +func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) { + var ( + adapterPods []*Pod + pods []*libpod.Pod + err error + ) + + if len(c.InputArgs) > 0 || c.Latest || c.All { + pods, err = shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime) + } else { + pods, err = r.Runtime.GetRunningPods() + } + if err != nil { + return nil, err + } + // convert libpod pods to adapter pods + for _, p := range pods { + adapterPod := Pod{ + p, + } + adapterPods = append(adapterPods, &adapterPod) + } + return adapterPods, nil +} diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 220f7163f..ef8de90a6 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/varlinkapi" "github.com/pkg/errors" "github.com/ulule/deepcopier" ) @@ -21,6 +22,13 @@ type Pod struct { remotepod } +// PodContainerStats is struct containing an adapter Pod and a libpod +// ContainerStats and is used primarily for outputing pod stats. +type PodContainerStats struct { + Pod *Pod + ContainerStats map[string]*libpod.ContainerStats +} + type remotepod struct { config *libpod.PodConfig state *libpod.PodInspectState @@ -399,3 +407,103 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV } return restartIDs, nil, restartErrors } + +// PodTop gets top statistics for a pod +func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) { + var ( + latest bool + podName string + ) + if c.Latest { + latest = true + } else { + podName = c.InputArgs[0] + } + return iopodman.TopPod().Call(r.Conn, podName, latest, descriptors) +} + +// GetStatPods returns pods for use in pod stats +func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) { + var ( + pods []*Pod + err error + podIDs []string + running bool + ) + + if len(c.InputArgs) > 0 || c.Latest || c.All { + podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs) + } else { + podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, true, false, []string{}) + running = true + } + if err != nil { + return nil, err + } + for _, p := range podIDs { + pod, err := r.Inspect(p) + if err != nil { + return nil, err + } + if running { + status, err := pod.GetPodStatus() + if err != nil { + // if we cannot get the status of the pod, skip and move on + continue + } + if strings.ToUpper(status) != "RUNNING" { + // if the pod is not running, skip and move on as well + continue + } + } + pods = append(pods, pod) + } + return pods, nil +} + +// GetPodStats returns the stats for each of its containers +func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerStats) (map[string]*libpod.ContainerStats, error) { + var ( + ok bool + prevStat *libpod.ContainerStats + ) + newContainerStats := make(map[string]*libpod.ContainerStats) + containers, err := p.AllContainers() + if err != nil { + return nil, err + } + for _, c := range containers { + if prevStat, ok = previousContainerStats[c.ID()]; !ok { + prevStat = &libpod.ContainerStats{ContainerID: c.ID()} + } + cStats := iopodman.ContainerStats{ + Id: prevStat.ContainerID, + Name: prevStat.Name, + Cpu: prevStat.CPU, + Cpu_nano: int64(prevStat.CPUNano), + System_nano: int64(prevStat.SystemNano), + Mem_usage: int64(prevStat.MemUsage), + Mem_limit: int64(prevStat.MemLimit), + Mem_perc: prevStat.MemPerc, + Net_input: int64(prevStat.NetInput), + Net_output: int64(prevStat.NetOutput), + Block_input: int64(prevStat.BlockInput), + Block_output: int64(prevStat.BlockOutput), + Pids: int64(prevStat.PIDs), + } + stats, err := iopodman.GetContainerStatsWithHistory().Call(p.Runtime.Conn, cStats) + if err != nil { + return nil, err + } + newStats := varlinkapi.ContainerStatsToLibpodContainerStats(stats) + // If the container wasn't running, don't include it + // but also suppress the error + if err != nil && errors.Cause(err) != libpod.ErrCtrStateInvalid { + return nil, err + } + if err == nil { + newContainerStats[c.ID()] = &newStats + } + } + return newContainerStats, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 8624981b1..6a68a3aea 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -3,11 +3,13 @@ package adapter import ( + "bufio" "context" "io" "io/ioutil" "os" "strconv" + "text/template" "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" @@ -16,7 +18,9 @@ import ( "github.com/containers/image/types" "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/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" @@ -108,15 +112,6 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for return r.Runtime.RemoveImage(ctx, img.Image, force) } -// LookupContainer ... -func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { - ctr, err := r.Runtime.LookupContainer(idOrName) - if err != nil { - return nil, err - } - return &Container{ctr}, nil -} - // PruneImages is wrapper into PruneImages within the image pkg func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return r.ImageRuntime().PruneImages(all) @@ -264,7 +259,7 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti if err != nil { return errors.Wrapf(err, "error parsing namespace-related options") } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command) + usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, options.Isolation) if err != nil { return errors.Wrapf(err, "error parsing ID mapping options") } @@ -341,3 +336,97 @@ func IsImageNotFound(err error) bool { } return false } + +// HealthCheck is a wrapper to same named function in libpod +func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) { + 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 ( + fromStart bool + eventsError error + ) + options, err := shared.GenerateEventOptions(c.Filter, c.Since, c.Until) + if err != nil { + return errors.Wrapf(err, "unable to generate event options") + } + tmpl, err := template.New("events").Parse(c.Format) + if err != nil { + return err + } + if len(c.Since) > 0 || len(c.Until) > 0 { + fromStart = true + } + eventChannel := make(chan *events.Event) + go func() { + eventsError = r.Runtime.Events(fromStart, c.Stream, options, eventChannel) + }() + + if eventsError != nil { + return eventsError + } + if err != nil { + return errors.Wrapf(err, "unable to tail the events log") + } + w := bufio.NewWriter(os.Stdout) + for event := range eventChannel { + if len(c.Format) > 0 { + if err := tmpl.Execute(w, event); err != nil { + return err + } + } else { + if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil { + return err + } + } + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + } + return nil +} diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 29b43e9b0..6c53d0c62 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -5,12 +5,12 @@ package adapter import ( "bufio" "context" - "encoding/json" "fmt" "io" "io/ioutil" "os" "strings" + "text/template" "time" "github.com/containers/buildah/imagebuildah" @@ -19,6 +19,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" @@ -49,14 +50,13 @@ func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { if err != nil { return nil, err } - rr := RemoteRuntime{ - Conn: conn, - Remote: true, - } - foo := LocalRuntime{ - &rr, - } - return &foo, nil + + return &LocalRuntime{ + &RemoteRuntime{ + Conn: conn, + Remote: true, + }, + }, nil } // Shutdown is a bogus wrapper for compat with the libpod runtime @@ -315,66 +315,6 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) return imageHistories, nil } -// LookupContainer gets basic information about container over a varlink -// connection and then translates it to a *Container -func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { - state, err := r.ContainerState(idOrName) - if err != nil { - return nil, err - } - config := r.Config(idOrName) - if err != nil { - return nil, err - } - - rc := remoteContainer{ - r, - config, - state, - } - - c := Container{ - rc, - } - return &c, nil -} - -func (r *LocalRuntime) GetLatestContainer() (*Container, error) { - return nil, libpod.ErrNotImplemented -} - -// ContainerState returns the "state" of the container. -func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, error) { //no-lint - reply, err := iopodman.ContainerStateData().Call(r.Conn, name) - if err != nil { - return nil, err - } - data := libpod.ContainerState{} - if err := json.Unmarshal([]byte(reply), &data); err != nil { - return nil, err - } - return &data, err - -} - -// Config returns a container config -func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { - // TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer - // further looking into it for after devconf. - // The libpod function for this has no errors so we are kind of in a tough - // spot here. Logging the errors for now. - reply, err := iopodman.ContainerConfig().Call(r.Conn, name) - if err != nil { - logrus.Error("call to container.config failed") - } - data := libpod.ContainerConfig{} - if err := json.Unmarshal([]byte(reply), &data); err != nil { - logrus.Error("failed to unmarshal container inspect data") - } - return &data - -} - // PruneImages is the wrapper call for a remote-client to prune images func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return iopodman.ImagesPrune().Call(r.Conn, all) @@ -808,3 +748,85 @@ func IsImageNotFound(err error) bool { } return false } + +// HealthCheck executes a container's healthcheck over a varlink connection +func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) { + 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 + if c.Stream { + more = uint64(varlink.More) + } + reply, err := iopodman.GetEvents().Send(r.Conn, more, c.Filter, c.Since, c.Until) + if err != nil { + return errors.Wrapf(err, "unable to obtain events") + } + + w := bufio.NewWriter(os.Stdout) + tmpl, err := template.New("events").Parse(c.Format) + if err != nil { + return err + } + + for { + returnedEvent, flags, err := reply() + if err != nil { + // When the error handling is back into podman, we can flip this to a better way to check + // for problems. For now, this works. + return err + } + if returnedEvent.Time == "" && returnedEvent.Status == "" && returnedEvent.Type == "" { + // We got a blank event return, signals end of stream in certain cases + break + } + eTime, err := time.Parse(time.RFC3339Nano, returnedEvent.Time) + if err != nil { + return errors.Wrapf(err, "unable to parse time of event %s", returnedEvent.Time) + } + eType, err := events.StringToType(returnedEvent.Type) + if err != nil { + return err + } + eStatus, err := events.StringToStatus(returnedEvent.Status) + if err != nil { + return err + } + event := events.Event{ + ID: returnedEvent.Id, + Image: returnedEvent.Image, + Name: returnedEvent.Name, + Status: eStatus, + Time: eTime, + Type: eType, + } + if len(c.Format) > 0 { + if err := tmpl.Execute(w, event); err != nil { + return err + } + } else { + if _, err := w.Write([]byte(event.ToHumanReadable())); err != nil { + return err + } + } + if _, err := w.Write([]byte("\n")); err != nil { + return err + } + if err := w.Flush(); err != nil { + return err + } + if flags&varlink.Continues == 0 { + break + } + } + return nil +} diff --git a/pkg/adapter/shortcuts/shortcuts.go b/pkg/adapter/shortcuts/shortcuts.go index 0633399ae..677d88457 100644 --- a/pkg/adapter/shortcuts/shortcuts.go +++ b/pkg/adapter/shortcuts/shortcuts.go @@ -25,3 +25,30 @@ func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) } return outpods, nil } + +// GetContainersByContext gets pods whether all, latest, or a slice of names/ids +func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) ([]*libpod.Container, error) { + var ctrs = []*libpod.Container{} + + if all { + return runtime.GetAllContainers() + } + + if latest { + c, err := runtime.GetLatestContainer() + if err != nil { + return nil, err + } + ctrs = append(ctrs, c) + return ctrs, nil + } + + for _, c := range names { + ctr, err := runtime.LookupContainer(c) + if err != nil { + return nil, err + } + ctrs = append(ctrs, ctr) + } + return ctrs, nil +} diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go index dcb7738be..270e431ad 100644 --- a/pkg/inspect/inspect.go +++ b/pkg/inspect/inspect.go @@ -3,11 +3,12 @@ package inspect import ( "time" + "github.com/containers/image/manifest" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-connections/nat" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/specs-go/v1" - specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-spec/specs-go" ) // ContainerData holds the podman inspect data for a container @@ -78,24 +79,25 @@ type HostConfig struct { // CtrConfig holds information about the container configuration type CtrConfig struct { - Hostname string `json:"Hostname"` - DomainName string `json:"Domainname"` //TODO - User specs.User `json:"User"` - AttachStdin bool `json:"AttachStdin"` //TODO - AttachStdout bool `json:"AttachStdout"` //TODO - AttachStderr bool `json:"AttachStderr"` //TODO - Tty bool `json:"Tty"` - OpenStdin bool `json:"OpenStdin"` - StdinOnce bool `json:"StdinOnce"` //TODO - Env []string `json:"Env"` - Cmd []string `json:"Cmd"` - Image string `json:"Image"` - Volumes map[string]struct{} `json:"Volumes"` - WorkingDir string `json:"WorkingDir"` - Entrypoint string `json:"Entrypoint"` - Labels map[string]string `json:"Labels"` - Annotations map[string]string `json:"Annotations"` - StopSignal uint `json:"StopSignal"` + Hostname string `json:"Hostname"` + DomainName string `json:"Domainname"` //TODO + User specs.User `json:"User"` + AttachStdin bool `json:"AttachStdin"` //TODO + AttachStdout bool `json:"AttachStdout"` //TODO + AttachStderr bool `json:"AttachStderr"` //TODO + Tty bool `json:"Tty"` + OpenStdin bool `json:"OpenStdin"` + StdinOnce bool `json:"StdinOnce"` //TODO + Env []string `json:"Env"` + Cmd []string `json:"Cmd"` + Image string `json:"Image"` + Volumes map[string]struct{} `json:"Volumes"` + WorkingDir string `json:"WorkingDir"` + Entrypoint string `json:"Entrypoint"` + Labels map[string]string `json:"Labels"` + Annotations map[string]string `json:"Annotations"` + StopSignal uint `json:"StopSignal"` + Healthcheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"` } // LogConfig holds the log information for a container @@ -156,6 +158,7 @@ type ContainerInspectData struct { HostsPath string `json:"HostsPath"` StaticDir string `json:"StaticDir"` LogPath string `json:"LogPath"` + ConmonPidFile string `json:"ConmonPidFile"` Name string `json:"Name"` RestartCount int32 `json:"RestartCount"` //TODO Driver string `json:"Driver"` @@ -178,18 +181,19 @@ type ContainerInspectData struct { // ContainerInspectState represents the state of a container. type ContainerInspectState struct { - OciVersion string `json:"OciVersion"` - Status string `json:"Status"` - Running bool `json:"Running"` - Paused bool `json:"Paused"` - Restarting bool `json:"Restarting"` // TODO - OOMKilled bool `json:"OOMKilled"` - Dead bool `json:"Dead"` - Pid int `json:"Pid"` - ExitCode int32 `json:"ExitCode"` - Error string `json:"Error"` // TODO - StartedAt time.Time `json:"StartedAt"` - FinishedAt time.Time `json:"FinishedAt"` + OciVersion string `json:"OciVersion"` + Status string `json:"Status"` + Running bool `json:"Running"` + Paused bool `json:"Paused"` + Restarting bool `json:"Restarting"` // TODO + OOMKilled bool `json:"OOMKilled"` + Dead bool `json:"Dead"` + Pid int `json:"Pid"` + ExitCode int32 `json:"ExitCode"` + Error string `json:"Error"` // TODO + StartedAt time.Time `json:"StartedAt"` + FinishedAt time.Time `json:"FinishedAt"` + Healthcheck HealthCheckResults `json:"Healthcheck,omitempty"` } // NetworkSettings holds information about the newtwork settings of the container @@ -227,3 +231,25 @@ type ImageResult struct { Labels map[string]string Dangling bool } + +// HealthCheckResults describes the results/logs from a healthcheck +type HealthCheckResults struct { + // Status healthy or unhealthy + Status string `json:"Status"` + // FailingStreak is the number of consecutive failed healthchecks + FailingStreak int `json:"FailingStreak"` + // Log describes healthcheck attempts and results + Log []HealthCheckLog `json:"Log"` +} + +// HealthCheckLog describes the results of a single healthcheck +type HealthCheckLog struct { + // Start time as string + Start string `json:"Start"` + // End time as a string + End string `json:"End"` + // Exitcode is 0 or 1 + ExitCode int `json:"ExitCode"` + // Output is the stdout/stderr from the healthcheck command + Output string `json:"Output"` +} diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index b104c592b..7fb5c7ea8 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -35,7 +35,10 @@ import ( const ( // timeFormat is the time format used in the log. - timeFormat = time.RFC3339Nano + // It is a modified version of RFC3339Nano that guarantees trailing + // zeroes are not trimmed, taken from + // https://github.com/golang/go/issues/19635 + timeFormat = "2006-01-02T15:04:05.000000000Z07:00" ) // LogStreamType is the type of the stream in CRI container log. @@ -277,10 +280,11 @@ func readLog(reader *bufio.Reader, opts *LogOptions) []string { // logWriter controls the writing into the stream based on the log options. type logWriter struct { - stdout io.Writer - stderr io.Writer - opts *LogOptions - remain int64 + stdout io.Writer + stderr io.Writer + opts *LogOptions + remain int64 + doAppend bool } // errMaximumWrite is returned when all bytes have been written. @@ -309,9 +313,15 @@ func (w *logWriter) write(msg *logMessage) error { return nil } line := msg.log - if w.opts.Timestamps { + if w.opts.Timestamps && !w.doAppend { prefix := append([]byte(msg.timestamp.Format(timeFormat)), delimiter[0]) line = append(prefix, line...) + if len(line) > 0 && line[len(line)-1] != '\n' { + w.doAppend = true + } + } + if w.doAppend && len(line) > 0 && line[len(line)-1] == '\n' { + w.doAppend = false } // If the line is longer than the remaining bytes, cut it. if int64(len(line)) > w.remain { diff --git a/pkg/rootless/rootless.go b/pkg/rootless/rootless.go new file mode 100644 index 000000000..a531e43ce --- /dev/null +++ b/pkg/rootless/rootless.go @@ -0,0 +1,9 @@ +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 dfbc7fe33..ff39e9e77 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -32,7 +32,11 @@ syscall_setresgid (gid_t rgid, gid_t egid, gid_t sgid) static int syscall_clone (unsigned long flags, void *child_stack) { +#if defined(__s390__) || defined(__CRIS__) + return (int) syscall (__NR_clone, child_stack, flags); +#else return (int) syscall (__NR_clone, flags, child_stack); +#endif } static char ** @@ -273,6 +277,8 @@ reexec_in_user_namespace (int ready) _exit (EXIT_FAILURE); } close (ready); + if (b != '1') + _exit (EXIT_FAILURE); if (syscall_setresgid (0, 0, 0) < 0) { diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 98692707f..baceebee3 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -11,7 +11,6 @@ import ( "os/user" "runtime" "strconv" - "strings" "sync" "syscall" "unsafe" @@ -61,6 +60,11 @@ func SkipStorageSetup() bool { return skipStorageSetup } +// Argument returns the argument that was set for the rootless session. +func Argument() string { + return os.Getenv("_LIBPOD_ROOTLESS_ARG") +} + // GetRootlessUID returns the UID of the user in the parent userNS func GetRootlessUID() int { uidEnv := os.Getenv("_LIBPOD_ROOTLESS_UID") @@ -68,7 +72,7 @@ func GetRootlessUID() int { u, _ := strconv.Atoi(uidEnv) return u } - return os.Getuid() + return os.Geteuid() } func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) error { @@ -102,7 +106,7 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap) // JoinNS re-exec podman in a new userNS and join the user namespace of the specified // PID. -func JoinNS(pid uint) (bool, int, error) { +func JoinNS(pid uint, preserveFDs int) (bool, int, error) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -117,6 +121,13 @@ func JoinNS(pid uint) (bool, int, error) { if int(pidC) < 0 { return false, -1, errors.Errorf("cannot re-exec process") } + 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() + } + } ret := C.reexec_in_user_namespace_wait(pidC) if ret < 0 { @@ -128,8 +139,16 @@ func JoinNS(pid uint) (bool, int, error) { // 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. +// 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) +} + +// 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) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { return false, -1, nil } @@ -146,6 +165,12 @@ func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { } defer userNS.Close() + if opts != nil && opts.Argument != "" { + if err := os.Setenv("_LIBPOD_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") @@ -185,27 +210,19 @@ func JoinNSPath(path string) (bool, int, error) { return true, int(ret), nil } -const defaultMinimumMappings = 65536 - -func getMinimumIDs(p string) int { - content, err := ioutil.ReadFile(p) - if err != nil { - logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings) - return defaultMinimumMappings - } - ret, err := strconv.Atoi(strings.TrimSuffix(string(content), "\n")) - if err != nil { - logrus.Debugf("error reading data from %q, use a default value of %d", p, defaultMinimumMappings) - return defaultMinimumMappings - } - return ret + 1 -} - // 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. +// 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) { if os.Geteuid() == 0 || os.Getenv("_LIBPOD_USERNS_CONFIGURED") != "" { if os.Getenv("_LIBPOD_USERNS_CONFIGURED") == "init" { return false, 0, runInUser() @@ -222,6 +239,13 @@ func BecomeRootInUserNS() (bool, int, error) { } defer r.Close() defer w.Close() + defer w.Write([]byte("0")) + + if opts != nil && opts.Argument != "" { + if err := os.Setenv("_LIBPOD_ROOTLESS_ARG", opts.Argument); err != nil { + return false, -1, err + } + } pidC := C.reexec_in_user_namespace(C.int(r.Fd())) pid := int(pidC) @@ -229,47 +253,18 @@ func BecomeRootInUserNS() (bool, int, error) { return false, -1, errors.Errorf("cannot re-exec process") } - allowSingleIDMapping := os.Getenv("PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS") != "" - var uids, gids []idtools.IDMap username := os.Getenv("USER") if username == "" { user, err := user.LookupId(fmt.Sprintf("%d", os.Getuid())) - if err != nil && !allowSingleIDMapping { - if os.IsNotExist(err) { - return false, 0, errors.Wrapf(err, "/etc/subuid or /etc/subgid does not exist, see subuid/subgid man pages for information on these files") - } - return false, 0, errors.Wrapf(err, "could not find user by UID nor USER env was set") - } if err == nil { username = user.Username } } mappings, err := idtools.NewIDMappings(username, username) - if !allowSingleIDMapping { - if err != nil { - return false, -1, err - } - - availableGIDs, availableUIDs := 0, 0 - for _, i := range mappings.UIDs() { - availableUIDs += i.Size - } - - minUIDs := getMinimumIDs("/proc/sys/kernel/overflowuid") - if availableUIDs < minUIDs { - return false, 0, fmt.Errorf("not enough UIDs available for the user, at least %d are needed", minUIDs) - } - - for _, i := range mappings.GIDs() { - availableGIDs += i.Size - } - minGIDs := getMinimumIDs("/proc/sys/kernel/overflowgid") - if availableGIDs < minGIDs { - return false, 0, fmt.Errorf("not enough GIDs available for the user, at least %d are needed", minGIDs) - } - } - if err == nil { + if err != nil { + logrus.Warnf("cannot find mappings for user %s: %v", username, err) + } else { uids = mappings.UIDs() gids = mappings.GIDs() } @@ -277,12 +272,10 @@ func BecomeRootInUserNS() (bool, int, error) { uidsMapped := false if mappings != nil && uids != nil { err := tryMappingTool("newuidmap", pid, os.Getuid(), uids) - if !allowSingleIDMapping && err != nil { - return false, 0, err - } uidsMapped = err == nil } if !uidsMapped { + logrus.Warnf("using rootless single mapping into the namespace. This might break some images. Check /etc/subuid and /etc/subgid for adding subids") setgroups := fmt.Sprintf("/proc/%d/setgroups", pid) err = ioutil.WriteFile(setgroups, []byte("deny\n"), 0666) if err != nil { @@ -299,9 +292,6 @@ func BecomeRootInUserNS() (bool, int, error) { gidsMapped := false if mappings != nil && gids != nil { err := tryMappingTool("newgidmap", pid, os.Getgid(), gids) - if !allowSingleIDMapping && err != nil { - return false, 0, err - } gidsMapped = err == nil } if !gidsMapped { diff --git a/pkg/rootless/rootless_unsupported.go b/pkg/rootless/rootless_unsupported.go index 1823c023e..e01d7855c 100644 --- a/pkg/rootless/rootless_unsupported.go +++ b/pkg/rootless/rootless_unsupported.go @@ -11,10 +11,18 @@ func IsRootless() bool { return false } +// 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 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 BecomeRootInUserNS() (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os1") +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 @@ -33,19 +41,32 @@ func SkipStorageSetup() bool { // JoinNS re-exec podman in a new userNS and join the user namespace of the specified // PID. -func JoinNS(pid uint) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os2") +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 os3") + 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 // namespace of the specified PID without looking up its parent. Useful to join directly -// the conmon process. +// the conmon process. It is a convenience function for JoinDirectUserAndMountNSWithOpts +// with a default configuration. func JoinDirectUserAndMountNS(pid uint) (bool, int, error) { - return false, -1, errors.New("this function is not supported on this os4") + 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/secrets/secrets.go b/pkg/secrets/secrets.go deleted file mode 100644 index 3b64f8952..000000000 --- a/pkg/secrets/secrets.go +++ /dev/null @@ -1,323 +0,0 @@ -package secrets - -import ( - "bufio" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/storage/pkg/idtools" - rspec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -var ( - // DefaultMountsFile holds the default mount paths in the form - // "host_path:container_path" - DefaultMountsFile = "/usr/share/containers/mounts.conf" - // OverrideMountsFile holds the default mount paths in the form - // "host_path:container_path" overridden by the user - OverrideMountsFile = "/etc/containers/mounts.conf" - // UserOverrideMountsFile holds the default mount paths in the form - // "host_path:container_path" overridden by the rootless user - UserOverrideMountsFile = filepath.Join(os.Getenv("HOME"), ".config/containers/mounts.conf") -) - -// secretData stores the name of the file and the content read from it -type secretData struct { - name string - data []byte -} - -// saveTo saves secret data to given directory -func (s secretData) saveTo(dir string) error { - path := filepath.Join(dir, s.name) - if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil && !os.IsExist(err) { - return err - } - return ioutil.WriteFile(path, s.data, 0700) -} - -func readAll(root, prefix string) ([]secretData, error) { - path := filepath.Join(root, prefix) - - data := []secretData{} - - files, err := ioutil.ReadDir(path) - if err != nil { - if os.IsNotExist(err) { - return data, nil - } - - return nil, err - } - - for _, f := range files { - fileData, err := readFile(root, filepath.Join(prefix, f.Name())) - if err != nil { - // If the file did not exist, might be a dangling symlink - // Ignore the error - if os.IsNotExist(err) { - continue - } - return nil, err - } - data = append(data, fileData...) - } - - return data, nil -} - -func readFile(root, name string) ([]secretData, error) { - path := filepath.Join(root, name) - - s, err := os.Stat(path) - if err != nil { - return nil, err - } - - if s.IsDir() { - dirData, err := readAll(root, name) - if err != nil { - return nil, err - } - return dirData, nil - } - bytes, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - return []secretData{{name: name, data: bytes}}, nil -} - -func getHostSecretData(hostDir string) ([]secretData, error) { - var allSecrets []secretData - hostSecrets, err := readAll(hostDir, "") - if err != nil { - return nil, errors.Wrapf(err, "failed to read secrets from %q", hostDir) - } - return append(allSecrets, hostSecrets...), nil -} - -func getMounts(filePath string) []string { - file, err := os.Open(filePath) - if err != nil { - // This is expected on most systems - logrus.Debugf("file %q not found, skipping...", filePath) - return nil - } - defer file.Close() - scanner := bufio.NewScanner(file) - if err = scanner.Err(); err != nil { - logrus.Errorf("error reading file %q, %v skipping...", filePath, err) - return nil - } - var mounts []string - for scanner.Scan() { - mounts = append(mounts, scanner.Text()) - } - return mounts -} - -// getHostAndCtrDir separates the host:container paths -func getMountsMap(path string) (string, string, error) { - arr := strings.SplitN(path, ":", 2) - if len(arr) == 2 { - return arr[0], arr[1], nil - } - return "", "", errors.Errorf("unable to get host and container dir") -} - -// SecretMounts copies, adds, and mounts the secrets to the container root filesystem -func SecretMounts(mountLabel, containerWorkingDir, mountFile string) []rspec.Mount { - return SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, containerWorkingDir, 0, 0) -} - -// SecretMountsWithUIDGID specifies the uid/gid of the owner -func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPrefix string, uid, gid int) []rspec.Mount { - var ( - secretMounts []rspec.Mount - mountFiles []string - ) - // Add secrets from paths given in the mounts.conf files - // mountFile will have a value if the hidden --default-mounts-file flag is set - // Note for testing purposes only - if mountFile == "" { - mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...) - if rootless.IsRootless() { - mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...) - _, err := os.Stat(UserOverrideMountsFile) - if err != nil && os.IsNotExist(err) { - os.MkdirAll(filepath.Dir(UserOverrideMountsFile), 0755) - if f, err := os.Create(UserOverrideMountsFile); err != nil { - logrus.Warnf("could not create file %s: %v", UserOverrideMountsFile, err) - } else { - f.Close() - } - } - } - } else { - mountFiles = append(mountFiles, mountFile) - } - for _, file := range mountFiles { - if _, err := os.Stat(file); err == nil { - mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, mountPrefix, uid, gid) - if err != nil { - logrus.Warnf("error mounting secrets, skipping: %v", err) - } - secretMounts = mounts - break - } - } - - // Add FIPS mode secret if /etc/system-fips exists on the host - _, err := os.Stat("/etc/system-fips") - if err == nil { - if err := addFIPSModeSecret(&secretMounts, containerWorkingDir, mountPrefix, mountLabel, uid, gid); err != nil { - logrus.Errorf("error adding FIPS mode secret to container: %v", err) - } - } else if os.IsNotExist(err) { - logrus.Debug("/etc/system-fips does not exist on host, not mounting FIPS mode secret") - } else { - logrus.Errorf("stat /etc/system-fips failed for FIPS mode secret: %v", err) - } - return secretMounts -} - -func rchown(chowndir string, uid, gid int) error { - return filepath.Walk(chowndir, func(filePath string, f os.FileInfo, err error) error { - return os.Lchown(filePath, uid, gid) - }) -} - -// addSecretsFromMountsFile copies the contents of host directory to container directory -// and returns a list of mounts -func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir, mountPrefix string, uid, gid int) ([]rspec.Mount, error) { - var mounts []rspec.Mount - defaultMountsPaths := getMounts(filePath) - for _, path := range defaultMountsPaths { - hostDir, ctrDir, err := getMountsMap(path) - if err != nil { - return nil, err - } - // skip if the hostDir path doesn't exist - if _, err = os.Stat(hostDir); err != nil { - if os.IsNotExist(err) { - logrus.Warnf("Path %q from %q doesn't exist, skipping", hostDir, filePath) - continue - } - return nil, errors.Wrapf(err, "failed to stat %q", hostDir) - } - - ctrDirOnHost := filepath.Join(containerWorkingDir, ctrDir) - - // In the event of a restart, don't want to copy secrets over again as they already would exist in ctrDirOnHost - _, err = os.Stat(ctrDirOnHost) - if os.IsNotExist(err) { - if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { - return nil, errors.Wrapf(err, "making container directory %q failed", ctrDirOnHost) - } - hostDir, err = resolveSymbolicLink(hostDir) - if err != nil { - return nil, err - } - - data, err := getHostSecretData(hostDir) - if err != nil { - return nil, errors.Wrapf(err, "getting host secret data failed") - } - for _, s := range data { - if err := s.saveTo(ctrDirOnHost); err != nil { - return nil, errors.Wrapf(err, "error saving data to container filesystem on host %q", ctrDirOnHost) - } - } - - err = label.Relabel(ctrDirOnHost, mountLabel, false) - if err != nil { - return nil, errors.Wrap(err, "error applying correct labels") - } - if uid != 0 || gid != 0 { - if err := rchown(ctrDirOnHost, uid, gid); err != nil { - return nil, err - } - } - } else if err != nil { - return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost) - } - - m := rspec.Mount{ - Source: filepath.Join(mountPrefix, ctrDir), - Destination: ctrDir, - Type: "bind", - Options: []string{"bind", "rprivate"}, - } - - mounts = append(mounts, m) - } - return mounts, nil -} - -// addFIPSModeSecret creates /run/secrets/system-fips in the container -// root filesystem if /etc/system-fips exists on hosts. -// This enables the container to be FIPS compliant and run openssl in -// FIPS mode as the host is also in FIPS mode. -func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir, mountPrefix, mountLabel string, uid, gid int) error { - secretsDir := "/run/secrets" - ctrDirOnHost := filepath.Join(containerWorkingDir, secretsDir) - if _, err := os.Stat(ctrDirOnHost); os.IsNotExist(err) { - if err = idtools.MkdirAllAs(ctrDirOnHost, 0755, uid, gid); err != nil { - return errors.Wrapf(err, "making container directory on host failed") - } - if err = label.Relabel(ctrDirOnHost, mountLabel, false); err != nil { - return errors.Wrap(err, "error applying correct labels") - } - } - fipsFile := filepath.Join(ctrDirOnHost, "system-fips") - // In the event of restart, it is possible for the FIPS mode file to already exist - if _, err := os.Stat(fipsFile); os.IsNotExist(err) { - file, err := os.Create(fipsFile) - if err != nil { - return errors.Wrapf(err, "error creating system-fips file in container for FIPS mode") - } - defer file.Close() - } - - if !mountExists(*mounts, secretsDir) { - m := rspec.Mount{ - Source: filepath.Join(mountPrefix, secretsDir), - Destination: secretsDir, - Type: "bind", - Options: []string{"bind", "rprivate"}, - } - *mounts = append(*mounts, m) - } - - return nil -} - -// mountExists checks if a mount already exists in the spec -func mountExists(mounts []rspec.Mount, dest string) bool { - for _, mount := range mounts { - if mount.Destination == dest { - return true - } - } - return false -} - -// resolveSymbolicLink resolves a possbile symlink path. If the path is a symlink, returns resolved -// path; if not, returns the original path. -func resolveSymbolicLink(path string) (string, error) { - info, err := os.Lstat(path) - if err != nil { - return "", err - } - if info.Mode()&os.ModeSymlink != os.ModeSymlink { - return path, nil - } - return filepath.EvalSymlinks(path) -} diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go index eccd41ff9..a1873086e 100644 --- a/pkg/spec/config_linux.go +++ b/pkg/spec/config_linux.go @@ -46,19 +46,32 @@ func devicesFromPath(g *generate.Generator, devicePath string) error { return errors.Wrapf(err, "cannot stat %s", devicePath) } if st.IsDir() { + found := false + src := resolvedDevicePath + dest := src + var devmode string + if len(devs) > 1 { + if len(devs[1]) > 0 && devs[1][0] == '/' { + dest = devs[1] + } else { + devmode = devs[1] + } + } if len(devs) > 2 { - return errors.Wrapf(unix.EINVAL, "not allowed to specify destination with a directory %s", devicePath) + if devmode != "" { + return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath) + } + devmode = devs[2] } - found := false + // mount the internal devices recursively if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error { if f.Mode()&os.ModeDevice == os.ModeDevice { found = true - device := dpath - - if len(devs) > 1 { - device = fmt.Sprintf("%s:%s", dpath, devs[1]) + device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src))) + if devmode != "" { + device = fmt.Sprintf("%s:%s", device, devmode) } if err := addDevice(g, device); err != nil { return errors.Wrapf(err, "failed to add %s device", dpath) diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 31039bfdf..118fbad72 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -9,6 +9,7 @@ import ( "strings" "syscall" + "github.com/containers/image/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" @@ -86,6 +87,7 @@ type CreateConfig struct { Env map[string]string //env ExposedPorts map[nat.Port]struct{} GroupAdd []string // group-add + HealthCheck *manifest.Schema2HealthConfig HostAdd []string //add-host Hostname string //hostname Image string @@ -361,7 +363,7 @@ func (c *CreateConfig) createExitCommand() []string { command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) } if c.Syslog { - command = append(command, "--syslog") + command = append(command, "--syslog", "true") } command = append(command, []string{"container", "cleanup"}...) @@ -559,6 +561,10 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l // Always use a cleanup process to clean up Podman after termination options = append(options, libpod.WithExitCommand(c.createExitCommand())) + if c.HealthCheck != nil { + options = append(options, libpod.WithHealthCheck(c.HealthCheck)) + logrus.Debugf("New container has a health check") + } return options, nil } diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 28a636fa6..a61741f73 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -9,7 +9,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" pmount "github.com/containers/storage/pkg/mount" - "github.com/docker/docker/daemon/caps" + "github.com/docker/docker/oci/caps" "github.com/docker/go-units" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -454,10 +454,6 @@ func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) { } func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) { - if config.PidMode.IsHost() && rootless.IsRootless() { - return - } - if !config.Privileged { for _, mp := range []string{ "/proc/acpi", @@ -469,10 +465,15 @@ func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) "/proc/sched_debug", "/proc/scsi", "/sys/firmware", + "/sys/fs/selinux", } { g.AddLinuxMaskedPaths(mp) } + if config.PidMode.IsHost() && rootless.IsRootless() { + return + } + for _, rp := range []string{ "/proc/asound", "/proc/bus", @@ -624,7 +625,7 @@ func setupCapabilities(config *CreateConfig, configSpec *spec.Spec) error { if useNotRoot(config.User) { configSpec.Process.Capabilities.Bounding = caplist } - caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop) + caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop, nil, false) if err != nil { return err } @@ -635,7 +636,7 @@ func setupCapabilities(config *CreateConfig, configSpec *spec.Spec) error { configSpec.Process.Capabilities.Effective = caplist configSpec.Process.Capabilities.Ambient = caplist if useNotRoot(config.User) { - caplist, err = caps.TweakCapabilities(bounding, config.CapAdd, config.CapDrop) + caplist, err = caps.TweakCapabilities(bounding, config.CapAdd, config.CapDrop, nil, false) if err != nil { return err } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index a4576191b..a408ad34b 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -7,6 +7,7 @@ import ( "path/filepath" "strings" "syscall" + "time" "github.com/BurntSushi/toml" "github.com/containers/image/types" @@ -189,15 +190,15 @@ func GetRootlessRuntimeDir() (string, error) { tmpDir := filepath.Join("/run", "user", uid) os.MkdirAll(tmpDir, 0700) st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir } } if runtimeDir == "" { - tmpDir := filepath.Join(os.TempDir(), "user", uid) + tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("run-%s", uid)) os.MkdirAll(tmpDir, 0700) st, err := os.Stat(tmpDir) - if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 { + if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && st.Mode().Perm() == 0700 { runtimeDir = tmpDir } } @@ -310,36 +311,37 @@ func GetDefaultStoreOptions() (storage.StoreOptions, error) { storageOpts = storage.StoreOptions{} storage.ReloadConfigurationFile(storageConf, &storageOpts) } - - if rootless.IsRootless() { - if os.IsNotExist(err) { - os.MkdirAll(filepath.Dir(storageConf), 0755) - file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) - if err != nil { - return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf) - } - - tomlConfiguration := getTomlStorage(&storageOpts) - defer file.Close() - enc := toml.NewEncoder(file) - if err := enc.Encode(tomlConfiguration); err != nil { - os.Remove(storageConf) - } - } else if err == nil { - // If the file did not specify a graphroot or runroot, - // set sane defaults so we don't try and use root-owned - // directories - if storageOpts.RunRoot == "" { - storageOpts.RunRoot = defaultRootlessRunRoot - } - if storageOpts.GraphRoot == "" { - storageOpts.GraphRoot = defaultRootlessGraphRoot - } + if rootless.IsRootless() && err == nil { + // If the file did not specify a graphroot or runroot, + // set sane defaults so we don't try and use root-owned + // directories + if storageOpts.RunRoot == "" { + storageOpts.RunRoot = defaultRootlessRunRoot + } + if storageOpts.GraphRoot == "" { + storageOpts.GraphRoot = defaultRootlessGraphRoot } } return storageOpts, nil } +// WriteStorageConfigFile writes the configuration to a file +func WriteStorageConfigFile(storageOpts *storage.StoreOptions, storageConf string) error { + os.MkdirAll(filepath.Dir(storageConf), 0755) + file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) + if err != nil { + return errors.Wrapf(err, "cannot open %s", storageConf) + } + tomlConfiguration := getTomlStorage(storageOpts) + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tomlConfiguration); err != nil { + os.Remove(storageConf) + return err + } + return nil +} + // StorageConfigFile returns the path to the storage config file used func StorageConfigFile() string { if rootless.IsRootless() { @@ -347,3 +349,25 @@ func StorageConfigFile() string { } return storage.DefaultConfigFile } + +// ParseInputTime takes the users input and to determine if it is valid and +// returns a time format and error. The input is compared to known time formats +// or a duration which implies no-duration +func ParseInputTime(inputTime string) (time.Time, error) { + timeFormats := []string{time.RFC3339Nano, time.RFC3339, "2006-01-02T15:04:05", "2006-01-02T15:04:05.999999999", + "2006-01-02Z07:00", "2006-01-02"} + // iterate the supported time formats + for _, tf := range timeFormats { + t, err := time.Parse(tf, inputTime) + if err == nil { + return t, nil + } + } + + // input might be a duration + duration, err := time.ParseDuration(inputTime) + if err != nil { + return time.Time{}, errors.Errorf("unable to interpret time value") + } + return time.Now().Add(-duration), nil +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index ad9f107a7..7a6ae3507 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -7,12 +7,14 @@ import ( "io" "io/ioutil" "os" + "sync" "syscall" "time" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" @@ -60,6 +62,21 @@ func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { return call.ReplyGetContainer(makeListContainer(ctr.ID(), batchInfo)) } +// GetContainersByContext returns a slice of container ids based on all, latest, or a list +func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { + var ids []string + + ctrs, err := shortcuts.GetContainersByContext(all, latest, input, i.Runtime) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + for _, c := range ctrs { + ids = append(ids, c.ID()) + } + return call.ReplyGetContainersByContext(ids) +} + // InspectContainer ... func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) @@ -344,17 +361,16 @@ func (i *LibpodAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) err } // WaitContainer ... -func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string) error { +func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string, interval int64) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) } - exitCode, err := ctr.Wait() + exitCode, err := ctr.WaitWithInterval(time.Duration(interval)) if err != nil { return call.ReplyErrorOccurred(err.Error()) } return call.ReplyWaitContainer(int64(exitCode)) - } // RemoveContainer ... @@ -503,12 +519,12 @@ func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifact } // ContainerInspectData returns the inspect data of a container in string format -func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string) error { +func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string, size bool) error { ctr, err := i.Runtime.LookupContainer(name) if err != nil { return call.ReplyContainerNotFound(name, err.Error()) } - data, err := ctr.Inspect(true) + data, err := ctr.Inspect(size) if err != nil { return call.ReplyErrorOccurred("unable to inspect container") } @@ -536,3 +552,107 @@ func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) e } return call.ReplyContainerStateData(string(b)) } + +// GetContainerStatsWithHistory is a varlink endpoint that returns container stats based on current and +// previous statistics +func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error { + con, err := i.Runtime.LookupContainer(prevStats.Id) + if err != nil { + return call.ReplyContainerNotFound(prevStats.Id, err.Error()) + } + previousStats := ContainerStatsToLibpodContainerStats(prevStats) + stats, err := con.GetContainerStats(&previousStats) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + cStats := iopodman.ContainerStats{ + Id: stats.ContainerID, + Name: stats.Name, + Cpu: stats.CPU, + Cpu_nano: int64(stats.CPUNano), + System_nano: int64(stats.SystemNano), + Mem_usage: int64(stats.MemUsage), + Mem_limit: int64(stats.MemLimit), + Mem_perc: stats.MemPerc, + Net_input: int64(stats.NetInput), + Net_output: int64(stats.NetOutput), + Block_input: int64(stats.BlockInput), + Block_output: int64(stats.BlockOutput), + Pids: int64(stats.PIDs), + } + 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 + if call.WantsMore() { + call.Continues = true + } + sinceTime, err := time.Parse(time.RFC3339Nano, since) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + options := libpod.LogOptions{ + Follow: follow, + Since: sinceTime, + Tail: uint64(tail), + Timestamps: timestamps, + } + + options.WaitGroup = &wg + if len(names) > 1 { + options.Multi = true + } + logChannel := make(chan *libpod.LogLine, int(tail)*len(names)+1) + containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if err := i.Runtime.Log(containers, &options, logChannel); err != nil { + return err + } + go func() { + wg.Wait() + close(logChannel) + }() + for line := range logChannel { + call.ReplyGetContainersLogs(newPodmanLogLine(line)) + if !call.Continues { + break + } + + } + return call.ReplyGetContainersLogs(iopodman.LogLine{}) +} + +func newPodmanLogLine(line *libpod.LogLine) iopodman.LogLine { + return iopodman.LogLine{ + Device: line.Device, + ParseLogType: line.ParseLogType, + Time: line.Time.Format(time.RFC3339Nano), + Msg: line.Msg, + Cid: line.CID, + } +} diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go new file mode 100644 index 000000000..47c628ead --- /dev/null +++ b/pkg/varlinkapi/events.go @@ -0,0 +1,58 @@ +package varlinkapi + +import ( + "fmt" + "time" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/events" +) + +// GetEvents is a remote endpoint to get events from the event log +func (i *LibpodAPI) GetEvents(call iopodman.VarlinkCall, filter []string, since string, until string) error { + var ( + fromStart bool + eventsError error + event *events.Event + stream bool + ) + if call.WantsMore() { + stream = true + call.Continues = true + } + filters, err := shared.GenerateEventOptions(filter, since, until) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + if len(since) > 0 || len(until) > 0 { + fromStart = true + } + eventChannel := make(chan *events.Event) + go func() { + eventsError = i.Runtime.Events(fromStart, stream, filters, eventChannel) + }() + if eventsError != nil { + return call.ReplyErrorOccurred(err.Error()) + } + for { + event = <-eventChannel + if event == nil { + call.Continues = false + break + } + call.ReplyGetEvents(iopodman.Event{ + Id: event.ID, + Image: event.Image, + Name: event.Name, + Status: fmt.Sprintf("%s", event.Status), + Time: event.Time.Format(time.RFC3339Nano), + Type: fmt.Sprintf("%s", event.Type), + }) + if !call.Continues { + // For a one-shot on events, we break out here + break + } + } + return nil +} diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 4ca4c4270..c79cee4c2 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -2,6 +2,7 @@ package varlinkapi import ( "encoding/json" + "fmt" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/rootless" "syscall" @@ -299,3 +300,33 @@ func (i *LibpodAPI) PodStateData(call iopodman.VarlinkCall, name string) error { } return call.ReplyPodStateData(string(b)) } + +// TopPod provides the top stats for a given or latest pod +func (i *LibpodAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, descriptors []string) error { + var ( + pod *libpod.Pod + err error + ) + if latest { + name = "latest" + pod, err = i.Runtime.GetLatestPod() + } else { + pod, err = i.Runtime.LookupPod(name) + } + if err != nil { + return call.ReplyPodNotFound(name, err.Error()) + } + + podStatus, err := shared.GetPodStatus(pod) + if err != nil { + return call.ReplyErrorOccurred(fmt.Sprintf("unable to get status for pod %s", pod.ID())) + } + if podStatus != "Running" { + return call.ReplyErrorOccurred("pod top can only be used on pods with at least one running container") + } + reply, err := pod.GetPodPidInformation(descriptors) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + return call.ReplyTopPod(reply) +} diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 3f32615ec..816143e9f 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -52,6 +52,7 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { Mem_free: host["MemFree"].(int64), Mem_total: host["MemTotal"].(int64), Swap_free: host["SwapFree"].(int64), + Swap_total: host["SwapTotal"].(int64), Arch: host["arch"].(string), Cpus: int64(host["cpus"].(int)), Hostname: host["hostname"].(string), diff --git a/test/README.md b/test/README.md index ef3bfbcf9..4e61a0774 100644 --- a/test/README.md +++ b/test/README.md @@ -43,6 +43,11 @@ Build ginkgo and install it under $GOPATH/bin with the following command: ``` GOPATH=~/go make .install.ginkgo ``` +If your PATH does not include $GOPATH/bin, you might consider adding it. + +``` +PATH=$PATH:$GOPATH/bin +``` # Integration Tests Test suite for integration test for podman command line. It has its own structs: @@ -63,21 +68,38 @@ GOPATH=~/go ginkgo -v test/e2e/. Note the trailing period on the command above. Also, **-v** invokes verbose mode. That switch is optional. + +### Running a single file of integration tests You can run a single file of integration tests using the go test command: ``` GOPATH=~/go go test -v test/e2e/libpod_suite_test.go test/e2e/common_test.go test/e2e/config.go test/e2e/config_amd64.go test/e2e/your_test.go ``` -#### Run all tests like PAPR -You can closely emulate the PAPR run for Fedora with the following command: +### Running a single integration test +Before running the test suite, you have to declare which test you want run in the test +file itself. Consider the following actual test: +``` +It("podman inspect bogus pod", func() { + session := podmanTest.Podman([]string{"pod", "inspect", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) +``` + +To mark this as the test you want run, you simply change the *It* description to *FIt*. Please note how +both the `F` and `I` are capitalized. + +You can run a single integration test using the same command we used to run all the tests in a single +file. ``` -make integration.fedora +GOPATH=~/go go test -v test/e2e/libpod_suite_test.go test/e2e/common_test.go test/e2e/config.go test/e2e/config_amd64.go test/e2e/your_test.go ``` -This will run lint, git-validation, and gofmt tests and then execute unit and integration -tests as well. +*Note*: Be sure you remove the `F` from the tests before committing your changes or you will skip all tests +in that file except the one with the `FIt` denotation. + ### Run tests in a container In case you have issue running the tests locally on your machine, you can run @@ -105,3 +127,7 @@ You can run the test with following command: ``` make localsystem ``` + +## Contributing to system tests + +Please see [the TODO list of needed workflows/tests](system/TODO.md). diff --git a/test/certs/README.md b/test/certs/README.md new file mode 100644 index 000000000..3aab01440 --- /dev/null +++ b/test/certs/README.md @@ -0,0 +1,9 @@ +# How to generate key and cert: + +## Make private key without a password + +certtool --rsa --generate-privkey --null-password --outfile=domain.key + +## Use ``domain.cfg`` template to make self-signed cert + +certtool --generate-self-signed --load-privkey=domain.key --template=domain.cfg --outfile=domain.crt --load-ca-privkey=domain.key --null-password --no-text diff --git a/test/certs/domain.cfg b/test/certs/domain.cfg new file mode 100644 index 000000000..5baeb5631 --- /dev/null +++ b/test/certs/domain.cfg @@ -0,0 +1,30 @@ +# X.509 Certificate options +organization = "Koko inc." +unit = "sleeping dept." +locality = "foobar" +state = "Attiki" +country = GR +cn = "Cindy Lauper" +uid = "clauper" +dc = "name" +dc = "domain" +serial = 1234 +dns_name = "localhost" +# Use -1 if there is no expiration date. +expiration_days = -1 +email = "none@none.org" +signing_key +encryption_key +cert_signing_key +crl_signing_key +data_encipherment +non_repudiation +tls_www_client +tls_www_server +code_signing_key +ocsp_signing_key +time_stamping_key +email_protection_key +ipsec_ike_key +# for any purpose (must not be used in intermediate CA certificates) +key_purpose_oid = 2.5.29.37.0 diff --git a/test/certs/domain.crt b/test/certs/domain.crt index 881fc124d..8a697d7b2 100644 --- a/test/certs/domain.crt +++ b/test/certs/domain.crt @@ -1,18 +1,33 @@ -----BEGIN CERTIFICATE----- -MIIC3zCCAmSgAwIBAgIUdbnvx7lLf8OANP37QTKoxfNAl5EwCgYIKoZIzj0EAwMw -gawxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1T -YW4gRnJhbmNpc2NvMSowKAYDVQQKEyFIb25lc3QgQWNobWVkJ3MgVXNlZCBDZXJ0 -aWZpY2F0ZXMxKTAnBgNVBAsTIEhhc3RpbHktR2VuZXJhdGVkIFZhbHVlcyBEaXZp -c29uMRkwFwYDVQQDExBBdXRvZ2VuZXJhdGVkIENBMB4XDTE4MDMyMDExMDUwMFoX -DTE5MDMyMDExMDUwMFowWzEVMBMGA1UEBxMMdGhlIGludGVybmV0MRYwFAYDVQQK -Ew1hdXRvZ2VuZXJhdGVkMRQwEgYDVQQLEwtwb2RtYW4gdGVzdDEUMBIGA1UEAxML -cG9kbWFuLXRlc3QwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATA65F+T8sreSnTm+I2 -IjeKN8rb5W2j3QKXz8n9JkPWiWX16HGIWso1JWPhhjvpmVkfSzD91niQwrsm6PhP -ypZUzkX5iL7JE8jVjflEiUbflSzc+fgT/scqRUUQ3evmqUCjgZYwgZMwDgYDVR0P -AQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMB -Af8EAjAAMB0GA1UdDgQWBBQCgkUh4aBOTl5KHettBluuE7rccDAfBgNVHSMEGDAW -gBTPyUqMxUVdwC4K+kh9jHtnf7GrETAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYI -KoZIzj0EAwMDaQAwZgIxAKsrYLbXSJs473tlfX3OF/BmfTvDwBO5TfPoZ1yNDhVk -UvoYn2szSEVMwR7uX1gKWgIxALz00G6umVkSh0MgIwSaYpJU/N1eVNgbIXRFV+5+ -lK/0jLWm4aAFkVhqUkkueTzG2g== +MIIFozCCBAugAwIBAgICBNIwDQYJKoZIhvcNAQELBQAwgboxFTATBgNVBAMTDENp +bmR5IExhdXBlcjEXMBUGCgmSJomT8ixkAQETB2NsYXVwZXIxFzAVBgNVBAsTDnNs +ZWVwaW5nIGRlcHQuMRIwEAYDVQQKEwlLb2tvIGluYy4xDzANBgNVBAcTBmZvb2Jh +cjEPMA0GA1UECBMGQXR0aWtpMQswCQYDVQQGEwJHUjEUMBIGCgmSJomT8ixkARkW +BG5hbWUxFjAUBgoJkiaJk/IsZAEZFgZkb21haW4wIBcNMTkwMzIwMTcyNjI4WhgP +OTk5OTEyMzEyMzU5NTlaMIG6MRUwEwYDVQQDEwxDaW5keSBMYXVwZXIxFzAVBgoJ +kiaJk/IsZAEBEwdjbGF1cGVyMRcwFQYDVQQLEw5zbGVlcGluZyBkZXB0LjESMBAG +A1UEChMJS29rbyBpbmMuMQ8wDQYDVQQHEwZmb29iYXIxDzANBgNVBAgTBkF0dGlr +aTELMAkGA1UEBhMCR1IxFDASBgoJkiaJk/IsZAEZFgRuYW1lMRYwFAYKCZImiZPy +LGQBGRYGZG9tYWluMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAsegi +5zWmOYejptJSm32ZV7ttJxSycbCIF8/dGTigFwE9IWMitWWz5VyohVYKYYV0ZZTI +SZOZFfL8ElQ6jTnHHxewWi/w1qINyTESGS2jOSBj3uXyGj94gXv9guzbWLNKAD8v +4iUHYq/7zZPyLqtzFGep3kuKFlrTxsdjgho2NyiuCCLcm+H3dpBoI24QIp1vCIX/ +LCqaAfkxzMyF5sawL7H82gAfYhvGTFoLb5Dy3HdUwlhelTW746HSqtTb9fEqUjNZ +MmBrCGexW8hN2WheIAoKOGc3UGZgpFj9q6LgWkVKsosZ9PhdxtOKH5XZrT6d5ZFb +HSfpsxZ0LMLDUK/H+kJaFJaSl0HyxbyZMDExtjCjLzk81+A9t9jkh8Jnag9Ylpec +ZHxKKl3K7lJuq7s69uKbN9XbTTUkmw4AMa7WdNadj/t8SfpM3ZxZWzX6U81wQg0Z +Zb/fKxu8lAMZNxfgW7bnaIUBAFzNd4AE/mCOO941v8cizXCNBZ5EyZGVIhxtAgMB +AAGjga4wgaswVQYDVR0lBE4wTAYEVR0lAAYIKwYBBQUHAwIGCCsGAQUFBwMBBggr +BgEFBQcDEQYIKwYBBQUHAwkGCCsGAQUFBwMDBggrBgEFBQcDCAYIKwYBBQUHAwQw +DAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwDwYDVR0PAQH/BAUD +AwfwADAdBgNVHQ4EFgQU28OkcdKIoWfHK19QXiFCYud345YwDQYJKoZIhvcNAQEL +BQADggGBAJyA0YiSiCzVS6M+/2hEvKamlYUz6L7pb5fdlQ3dPL/KlbUPc31bLD7b +G3Su9iz1bL1qVGebgoJTEDOD/6F9DbMY4Nsh4oe1rS+eBZe6fG6VnaxE4U2XIWKD +0aijGGfUvEB8W66BtCrtjY3bAb9v1OLvLxktd9do4ACjskq+q+CE/8ID9tCkAn8J +q0aHv5a5H3bPu3WGuiifZHER+08OwEsO7jsnZAJdgrAK8D0Rodd1rbdIFh2klu1p +LOCCMhgrQtCFVhoqWA2XaGexqmlTTXjabFDyMA8RvdKozwLSpyMMHJX6Q38Wdr8Z +nAXfqkCAcXuVwk2llAW7f073Ze3xFWUiW6gH0w7H1KvHHkglBM4H3HyG2T2pu9lb +mrqRRnfx2AuQplUrFVvz5V7sKmTwZCPueXkfcv/+6bxcLA0N0p3Dk+LGc2JzBLFb +n6AzrjKwiEg5ri4dQEuaYnJAYDvp+/vo4AEPrUc9nKwOo1h+3rAY2ZkuCy06/3cj +nzp56Tpaaw== -----END CERTIFICATE----- diff --git a/test/certs/domain.key b/test/certs/domain.key index b0f15eb75..4fe293cac 100644 --- a/test/certs/domain.key +++ b/test/certs/domain.key @@ -1,6 +1,39 @@ ------BEGIN EC PRIVATE KEY----- -MIGkAgEBBDB7ZI5Q6dOSwOqpJ2FVlFuDJN/sJB3epR2S+rOvCPua+rQ8uv6lpZDx -CQ4ioUMFo6agBwYFK4EEACKhZANiAATA65F+T8sreSnTm+I2IjeKN8rb5W2j3QKX -z8n9JkPWiWX16HGIWso1JWPhhjvpmVkfSzD91niQwrsm6PhPypZUzkX5iL7JE8jV -jflEiUbflSzc+fgT/scqRUUQ3evmqUA= ------END EC PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIG4gIBAAKCAYEAsegi5zWmOYejptJSm32ZV7ttJxSycbCIF8/dGTigFwE9IWMi +tWWz5VyohVYKYYV0ZZTISZOZFfL8ElQ6jTnHHxewWi/w1qINyTESGS2jOSBj3uXy +Gj94gXv9guzbWLNKAD8v4iUHYq/7zZPyLqtzFGep3kuKFlrTxsdjgho2NyiuCCLc +m+H3dpBoI24QIp1vCIX/LCqaAfkxzMyF5sawL7H82gAfYhvGTFoLb5Dy3HdUwlhe +lTW746HSqtTb9fEqUjNZMmBrCGexW8hN2WheIAoKOGc3UGZgpFj9q6LgWkVKsosZ +9PhdxtOKH5XZrT6d5ZFbHSfpsxZ0LMLDUK/H+kJaFJaSl0HyxbyZMDExtjCjLzk8 +1+A9t9jkh8Jnag9YlpecZHxKKl3K7lJuq7s69uKbN9XbTTUkmw4AMa7WdNadj/t8 +SfpM3ZxZWzX6U81wQg0ZZb/fKxu8lAMZNxfgW7bnaIUBAFzNd4AE/mCOO941v8ci +zXCNBZ5EyZGVIhxtAgMBAAECggGAfvporw2jrrwZGiBTxZdHs06bAaHMG0kcWaKK +9E1uNf00XHgddcs5MyOHRGO81Q4jnb0rlxg502iycYKcp9/tN0v5GuXMx+SyYj8b +48ynC0cLATSuL/3NTN3qe2ACzrRoxPRUgNxdARsKZhiKarUEVjQHEhpoXLxHG0GE +zH9Y4tWuITCAtOH7dixrp54O9iXX8gVxs1xUv8PUv4/aonR9nA01o4Mi4ytfxW8f +amnSbXjejjf0ihroF/iQHE4BEPEnSw/noudboZqW1fX30QJsbk13VH9BGIN2zQ06 +vnDU/VrUYfgKNGSjaeECaN7iZQ7nekzSomKFGm8yoLpgsFYjpqnGhyplDY1jE/J3 +BL4r7PVeUWiQdLqkjLxeAhcySUjGvt79UsX0N8Oa3dgOGUHz3H986l3G9Hlcgvnc +L18qzsyIo4WNFrLAWt3nwiWM8Olj3Y6S3S8EiBeXVe1g+nuYBFwLFDCkw26WhtJ0 +sy90q6pRvL1MXOq2etcT5RcNfichAoHBANXES2CtpE7GdgAnVpYoBUng0n8qieTG +qLkkPx5E9pr64c3pUqXVGjFY0XvZ16x0UMOC2+V1DuLea2WyPO0FmitWEXH7xfVN +KM+3ORT5afSG0fhaIDvkPdG3QwfGZCib9g5ZOR0rxnDHVAzCx242YITUa8rn8158 +tpH0nDsvvAnVeUIMHOq5DO7zx1jcPQtcWP4vF3a37j2dOgBe4c7CCMOpP8gCGVlX +Okr6HtNoKiKA4n5NbJNo+BKAJ8o7NrHdNwKBwQDVDiWqYve2kyGcRSowJk9gHH1f +Ls+0MP6gLvdRoIhAihrNLws8fhI/mLBxHTBCogoumU5nWavZyKqDTf0dRJSKgQfJ +hdkRPVWIy6crshtSRZ+dX7yFKAnXU+D+cgcximonA4Uza0WFh9OP4702VpcdlFQ8 +qmKZvFI1orLw37h8ej/Bk4MeocDBWjpa5Q/0Nk7JsuwyW/OadXM48NfJM88ZrEi/ +ZuDjNIshynG3tddoIs0nNfkoslFGOzxpMFnbRXsCgcBpEkwOoCsUAV684pkfw1oe +HyC4GtuelLsYDaXspe8k7E4THS1fj6iJOuP04XWuMZoFD4wwc+I2Ryc43GwwAMHv +rSV0BlIeKaf2uVOYaKPY6m/Ih9wyNBTiwRZ0euJ+R3KhSN/W485tXryEbTUDijzU +7WhyWqJ3/grrIPWt7d+aYdBxU2zfPsgJp8+DcPWcYO7pOZJp6yxyIpcA2aJaM2uF +aOqNz+JP1J01f02pkhirzvgFJt9IcZ8F0PI95+8Ra+8CgcAD6tesc1dkpv3mNqtY +6UtqU/vGJUEyafg0j8iCWrZGoYNupF/Lg/Hn83HDEqtRflM7mhwD8HUlcvgXo/Z0 +dE9a4JZ5ERn1pDAPbNctCYBRGfCeXyVDOYI80FEBvKz/LzFWeE0Zre5AT0gHjENt +XVg39gM6flODyh+k1tH9dc+ZklHbyE+P35+Arp0GENIjRmBaewy2vFQVUfWFZYBC +Nc6oBS/tPQIDi3LHc0Z1/0TvqDwnbWmgYu71oJ8yu+3bB0MCgcB0katYynavMg3W +w15sH/1+be5dFTXZoiajjqXmYDNkZzyLoghteu/eyxqFw8Au4hyVy5koYHN/jrzf +1ZfXFCs3P5asCAptTH3kwgT/ER58KPOoq7cGEaut9eQ1UAc8E34b/ZISbTl/Zw05 +swOMC6ipzn70kNoPcjJN2ujSUCD9t0NKyyCAKokjFICGpCHDOdOwqtZRbKFVFSix +/T4HAV9puGT6kDMCtjLyZ+fUOf9hgCHK3sBkBt5XrU8j6Wf/zyg= +-----END RSA PRIVATE KEY----- diff --git a/test/e2e/attach_test.go b/test/e2e/attach_test.go index 42866d5a1..a843fe7ff 100644 --- a/test/e2e/attach_test.go +++ b/test/e2e/attach_test.go @@ -3,8 +3,9 @@ package integration import ( - "fmt" "os" + "syscall" + "time" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -24,14 +25,14 @@ var _ = Describe("Podman attach", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -51,6 +52,16 @@ var _ = Describe("Podman attach", func() { Expect(results.ExitCode()).To(Equal(125)) }) + It("podman container attach to non-running container", func() { + session := podmanTest.Podman([]string{"container", "create", "--name", "test1", "-d", "-i", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + results := podmanTest.Podman([]string{"container", "attach", "test1"}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(125)) + }) + It("podman attach to multiple containers", func() { session := podmanTest.RunTopContainer("test1") session.WaitWithDefaultTimeout() @@ -64,4 +75,44 @@ var _ = Describe("Podman attach", func() { results.WaitWithDefaultTimeout() Expect(results.ExitCode()).To(Equal(125)) }) + + It("podman attach to a running container", func() { + session := podmanTest.Podman([]string{"run", "-d", "--name", "test", ALPINE, "/bin/sh", "-c", "while true; do echo test; sleep 1; done"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + results := podmanTest.Podman([]string{"attach", "test"}) + time.Sleep(2 * time.Second) + results.Signal(syscall.SIGTSTP) + Expect(results.OutputToString()).To(ContainSubstring("test")) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) + It("podman attach to the latest container", func() { + session := podmanTest.Podman([]string{"run", "-d", "--name", "test1", ALPINE, "/bin/sh", "-c", "while true; do echo test1; sleep 1; done"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-d", "--name", "test2", ALPINE, "/bin/sh", "-c", "while true; do echo test2; sleep 1; done"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + results := podmanTest.Podman([]string{"attach", "-l"}) + time.Sleep(2 * time.Second) + results.Signal(syscall.SIGTSTP) + Expect(results.OutputToString()).To(ContainSubstring("test2")) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(2)) + }) + + It("podman attach to a container with --sig-proxy set to false", func() { + session := podmanTest.Podman([]string{"run", "-d", "--name", "test", ALPINE, "/bin/sh", "-c", "while true; do echo test; sleep 1; done"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + results := podmanTest.Podman([]string{"attach", "--sig-proxy=false", "test"}) + time.Sleep(2 * time.Second) + results.Signal(syscall.SIGTERM) + results.WaitWithDefaultTimeout() + Expect(results.OutputToString()).To(ContainSubstring("test")) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1)) + }) }) diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go index 11332708b..5b549755e 100644 --- a/test/e2e/checkpoint_test.go +++ b/test/e2e/checkpoint_test.go @@ -3,9 +3,9 @@ package integration import ( - "fmt" "net" "os" + "os/exec" "github.com/containers/libpod/pkg/criu" . "github.com/containers/libpod/test/utils" @@ -26,7 +26,18 @@ var _ = Describe("Podman checkpoint", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() + // Check if the runtime implements checkpointing. Currently only + // runc's checkpoint/restore implementation is supported. + cmd := exec.Command(podmanTest.OCIRuntime, "checkpoint", "-h") + if err := cmd.Start(); err != nil { + Skip("OCI runtime does not support checkpoint/restore") + } + if err := cmd.Wait(); err != nil { + Skip("OCI runtime does not support checkpoint/restore") + } + if !criu.CheckForCriu() { Skip("CRIU is missing or too old.") } @@ -42,8 +53,8 @@ var _ = Describe("Podman checkpoint", func() { AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman checkpoint bogus container", func() { diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index 34b218621..bf9c88de5 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,14 @@ var _ = Describe("Podman commit", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -50,6 +49,21 @@ var _ = Describe("Podman commit", func() { Expect(StringInSlice("foobar.com/test1-image:latest", data[0].RepoTags)).To(BeTrue()) }) + It("podman container commit container", func() { + _, ec, _ := podmanTest.RunLsContainer("test1") + Expect(ec).To(Equal(0)) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + session := podmanTest.Podman([]string{"container", "commit", "test1", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + check := podmanTest.Podman([]string{"image", "inspect", "foobar.com/test1-image:latest"}) + check.WaitWithDefaultTimeout() + data := check.InspectImageJSON() + Expect(StringInSlice("foobar.com/test1-image:latest", data[0].RepoTags)).To(BeTrue()) + }) + It("podman commit container with message", func() { _, ec, _ := podmanTest.RunLsContainer("test1") Expect(ec).To(Equal(0)) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index b22ead3fa..b20b3b37e 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -3,12 +3,17 @@ package integration import ( "encoding/json" "fmt" + "github.com/containers/libpod/pkg/rootless" + "io/ioutil" "os" "os/exec" "path/filepath" + "sort" "strings" "testing" + "github.com/containers/storage" + "github.com/containers/libpod/pkg/inspect" . "github.com/containers/libpod/test/utils" "github.com/containers/storage/pkg/reexec" @@ -40,13 +45,33 @@ type PodmanTestIntegration struct { SignaturePolicyPath string CgroupManager string Host HostOS + Timings []string + TmpDir string } +var LockTmpDir string + // PodmanSessionIntegration sturct for command line session type PodmanSessionIntegration struct { *PodmanSession } +type testResult struct { + name string + length float64 +} + +type testResultsSorted []testResult + +func (a testResultsSorted) Len() int { return len(a) } +func (a testResultsSorted) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type testResultsSortedLength struct{ testResultsSorted } + +func (a testResultsSorted) Less(i, j int) bool { return a[i].length < a[j].length } + +var testResults []testResult + // TestLibpod ginkgo master function func TestLibpod(t *testing.T) { if reexec.Init() { @@ -60,7 +85,7 @@ func TestLibpod(t *testing.T) { RunSpecs(t, "Libpod Suite") } -var _ = BeforeSuite(func() { +var _ = SynchronizedBeforeSuite(func() []byte { //Cache images cwd, _ := os.Getwd() INTEGRATION_ROOT = filepath.Join(cwd, "../../") @@ -72,6 +97,7 @@ var _ = BeforeSuite(func() { os.Exit(1) } } + for _, image := range CACHE_IMAGES { if err := podman.CreateArtifact(image); err != nil { fmt.Printf("%q\n", err) @@ -92,6 +118,68 @@ var _ = BeforeSuite(func() { } f.Close() } + path, err := ioutil.TempDir("", "libpodlock") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + return []byte(path) +}, func(data []byte) { + LockTmpDir = string(data) +}) + +func (p *PodmanTestIntegration) Setup() { + cwd, _ := os.Getwd() + INTEGRATION_ROOT = filepath.Join(cwd, "../../") + p.ArtifactPath = ARTIFACT_DIR +} + +//var _ = BeforeSuite(func() { +// cwd, _ := os.Getwd() +// INTEGRATION_ROOT = filepath.Join(cwd, "../../") +// podman := PodmanTestCreate("/tmp") +// podman.ArtifactPath = ARTIFACT_DIR +// if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) { +// if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil { +// fmt.Printf("%q\n", err) +// os.Exit(1) +// } +// } +//}) +// for _, image := range CACHE_IMAGES { +// if err := podman.CreateArtifact(image); err != nil { +// fmt.Printf("%q\n", err) +// os.Exit(1) +// } +// } +// host := GetHostDistributionInfo() +// if host.Distribution == "rhel" && strings.HasPrefix(host.Version, "7") { +// f, err := os.OpenFile("/proc/sys/user/max_user_namespaces", os.O_WRONLY, 0644) +// if err != nil { +// fmt.Println("Unable to enable userspace on RHEL 7") +// os.Exit(1) +// } +// _, err = f.WriteString("15000") +// if err != nil { +// fmt.Println("Unable to enable userspace on RHEL 7") +// os.Exit(1) +// } +// f.Close() +// } +// path, err := ioutil.TempDir("", "libpodlock") +// if err != nil { +// fmt.Println(err) +// os.Exit(1) +// } +// LockTmpDir = path +//}) + +var _ = AfterSuite(func() { + sort.Sort(testResultsSortedLength{testResults}) + fmt.Println("integration timing results") + for _, result := range testResults { + fmt.Printf("%s\t\t%f\n", result.name, result.length) + } }) // PodmanTestCreate creates a PodmanTestIntegration instance for the tests @@ -126,7 +214,11 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { if os.Getenv("STORAGE_OPTIONS") != "" { storageOptions = os.Getenv("STORAGE_OPTIONS") } + cgroupManager := CGROUP_MANAGER + if rootless.IsRootless() { + cgroupManager = "cgroupfs" + } if os.Getenv("CGROUP_MANAGER") != "" { cgroupManager = os.Getenv("CGROUP_MANAGER") } @@ -147,7 +239,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { ociRuntime = "/usr/bin/runc" } } - + os.Setenv("DISABLE_HC_SYSTEMD", "true") CNIConfigDir := "/etc/cni/net.d" p := &PodmanTestIntegration{ @@ -159,6 +251,7 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { }, ConmonBinary: conmonBinary, CrioRoot: filepath.Join(tempDir, "crio"), + TmpDir: tempDir, CNIConfigDir: CNIConfigDir, OCIRuntime: ociRuntime, RunRoot: filepath.Join(tempDir, "crio-run"), @@ -220,3 +313,27 @@ func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { Expect(err).To(BeNil()) return i } + +// InspectContainer returns a container's inspect data in JSON format +func (p *PodmanTestIntegration) InspectContainer(name string) []inspect.ContainerData { + cmd := []string{"inspect", name} + session := p.Podman(cmd) + session.WaitWithDefaultTimeout() + return session.InspectContainerToJSON() +} + +func processTestResult(f GinkgoTestDescription) { + tr := testResult{length: f.Duration.Seconds(), name: f.TestText} + testResults = append(testResults, tr) +} + +func GetPortLock(port string) storage.Locker { + lockFile := filepath.Join(LockTmpDir, port) + lock, err := storage.GetLockfile(lockFile) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + lock.Lock() + return lock +} diff --git a/test/e2e/config.go b/test/e2e/config.go index 8116d993b..3fdb9e116 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -6,4 +6,5 @@ var ( ALPINE = "docker.io/library/alpine:latest" infra = "k8s.gcr.io/pause:3.1" BB = "docker.io/library/busybox:latest" + healthcheck = "docker.io/libpod/alpine_healthcheck:latest" ) diff --git a/test/e2e/config_amd64.go b/test/e2e/config_amd64.go index 3459bea6d..d02de7a6e 100644 --- a/test/e2e/config_amd64.go +++ b/test/e2e/config_amd64.go @@ -3,7 +3,7 @@ package integration var ( STORAGE_OPTIONS = "--storage-driver vfs" ROOTLESS_STORAGE_OPTIONS = "--storage-driver vfs" - CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels} + CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels, healthcheck} nginx = "quay.io/libpod/alpine_nginx:latest" BB_GLIBC = "docker.io/library/busybox:glibc" registry = "docker.io/library/registry:2" diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go index e1e760ee0..f89865264 100644 --- a/test/e2e/cp_test.go +++ b/test/e2e/cp_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "io/ioutil" "os" "os/exec" @@ -27,14 +26,15 @@ var _ = Describe("Podman cp", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman cp file", func() { diff --git a/test/e2e/create_staticip_test.go b/test/e2e/create_staticip_test.go index 9bdc30342..6c4ca1cb8 100644 --- a/test/e2e/create_staticip_test.go +++ b/test/e2e/create_staticip_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -19,11 +18,13 @@ var _ = Describe("Podman create with --ip flag", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() // Cleanup the CNI networks used by the tests os.RemoveAll("/var/lib/cni/networks/podman") @@ -32,8 +33,8 @@ var _ = Describe("Podman create with --ip flag", func() { AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("Podman create --ip with garbage address", func() { diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 9a526b778..6ed5ad2d8 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -25,14 +25,14 @@ var _ = Describe("Podman create", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -56,6 +56,13 @@ var _ = Describe("Podman create", func() { Expect(podmanTest.NumberOfContainers()).To(Equal(1)) }) + It("podman container create container based on a remote image", func() { + session := podmanTest.Podman([]string{"container", "create", BB_GLIBC, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + }) + It("podman create using short options", func() { session := podmanTest.Podman([]string{"create", ALPINE, "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/diff_test.go b/test/e2e/diff_test.go index 94e150467..fba65823e 100644 --- a/test/e2e/diff_test.go +++ b/test/e2e/diff_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "sort" @@ -25,14 +24,14 @@ var _ = Describe("Podman diff", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -43,6 +42,13 @@ var _ = Describe("Podman diff", func() { Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0)) }) + It("podman container diff of image", func() { + session := podmanTest.Podman([]string{"container", "diff", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 0)) + }) + It("podman diff bogus image", func() { session := podmanTest.Podman([]string{"diff", "1234"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/e2e.coverprofile b/test/e2e/e2e.coverprofile index b5382604f..d413679ea 100644 --- a/test/e2e/e2e.coverprofile +++ b/test/e2e/e2e.coverprofile @@ -1,11 +1,11 @@ mode: atomic -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:14.46,21.20 2 1 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:31.2,31.19 1 1 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:38.2,38.53 1 1 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:65.2,65.52 1 1 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:21.20,23.17 2 2 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:26.3,28.36 3 2 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:14.46,21.20 2 3 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:32.2,32.19 1 3 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:39.2,39.53 1 3 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:66.2,66.52 1 3 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:21.20,23.17 2 6 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:26.3,29.36 4 6 github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:23.17,25.4 1 0 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:31.19,36.3 4 2 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:38.53,63.3 20 1 -github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:65.52,90.3 20 1 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:32.19,37.3 3 6 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:39.53,64.3 20 3 +github.com/containers/libpod/test/e2e/pod_pod_namespaces.go:66.52,91.3 20 3
\ No newline at end of file diff --git a/test/e2e/events_test.go b/test/e2e/events_test.go new file mode 100644 index 000000000..321d93757 --- /dev/null +++ b/test/e2e/events_test.go @@ -0,0 +1,116 @@ +package integration + +import ( + "fmt" + "os" + "strings" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman events", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + + }) + + // For most, all, of these tests we do not "live" test following a log because it may make a fragile test + // system more complex. Instead we run the "events" and then verify that the events are processed correctly. + // Perhaps a future version of this test would put events in a go func and send output back over a channel + // while events occur. + It("podman events", func() { + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(BeZero()) + }) + + It("podman events with an event filter", func() { + SkipIfRemote() + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + + It("podman events with an event filter and container=cid", func() { + SkipIfRemote() + _, ec, cid := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + _, ec2, cid2 := podmanTest.RunLsContainer("") + Expect(ec2).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "event=start", "--filter", fmt.Sprintf("container=%s", cid)}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + Expect(!strings.Contains(result.OutputToString(), cid2)) + }) + + It("podman events with a type", func() { + SkipIfRemote() + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(0)) + }) + + It("podman events with a type", func() { + SkipIfRemote() + setup := podmanTest.Podman([]string{"run", "-dt", "--pod", "new:foobar", ALPINE, "top"}) + setup.WaitWithDefaultTimeout() + stop := podmanTest.Podman([]string{"pod", "stop", "foobar"}) + stop.WaitWithDefaultTimeout() + Expect(stop.ExitCode()).To(Equal(0)) + Expect(setup.ExitCode()).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false", "--filter", "type=pod"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + fmt.Println(result.OutputToStringArray()) + Expect(len(result.OutputToStringArray())).To(Equal(2)) + }) + + It("podman events --since", func() { + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1m"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(BeZero()) + }) + + It("podman events --until", func() { + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + test := podmanTest.Podman([]string{"events", "--help"}) + test.WaitWithDefaultTimeout() + fmt.Println(test.OutputToStringArray()) + result := podmanTest.Podman([]string{"events", "--stream=false", "--since", "1h"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(BeZero()) + }) + +}) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 5839b364d..2a10e52b1 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,14 @@ var _ = Describe("Podman exec", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -57,6 +56,16 @@ var _ = Describe("Podman exec", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman container exec simple command", func() { + setup := podmanTest.RunTopContainer("test1") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"container", "exec", "test1", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman exec simple command using latest", func() { setup := podmanTest.RunTopContainer("test1") setup.WaitWithDefaultTimeout() diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go index c4b5e4968..71c6c1820 100644 --- a/test/e2e/exists_test.go +++ b/test/e2e/exists_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,14 @@ var _ = Describe("Podman image|container exists", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) diff --git a/test/e2e/export_test.go b/test/e2e/export_test.go index dba0a2255..71ddb518a 100644 --- a/test/e2e/export_test.go +++ b/test/e2e/export_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" "path/filepath" @@ -23,14 +22,14 @@ var _ = Describe("Podman export", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -50,6 +49,22 @@ var _ = Describe("Podman export", func() { Expect(err).To(BeNil()) }) + It("podman container export output flag", func() { + SkipIfRemote() + _, ec, cid := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + + outfile := filepath.Join(podmanTest.TempDir, "container.tar") + result := podmanTest.Podman([]string{"container", "export", "-o", outfile, cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + _, err := os.Stat(outfile) + Expect(err).To(BeNil()) + + err = os.Remove(outfile) + Expect(err).To(BeNil()) + }) + It("podman export bad filename", func() { _, ec, cid := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 94e02dc55..5bcf3b347 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -25,14 +24,14 @@ var _ = Describe("Podman generate kube", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -49,6 +48,7 @@ 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,6 +62,7 @@ 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)) @@ -75,6 +76,7 @@ var _ = Describe("Podman generate kube", func() { }) It("podman generate kube on pod", func() { + SkipIfRootless() _, rc, _ := podmanTest.CreatePod("toppod") Expect(rc).To(Equal(0)) @@ -91,6 +93,7 @@ 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 new file mode 100644 index 000000000..cd2365ce7 --- /dev/null +++ b/test/e2e/healthcheck_run_test.go @@ -0,0 +1,182 @@ +// +build !remoteclient + +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman healthcheck run", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + + }) + + It("podman healthcheck run bogus container", func() { + session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Not(Equal(0))) + }) + + It("podman healthcheck on valid container", func() { + SkipIfRootless() + podmanTest.RestoreArtifact(healthcheck) + session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(0)) + }) + + It("podman healthcheck that should fail", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "docker.io/libpod/badhealthcheck:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + }) + + It("podman healthcheck on stopped container", func() { + podmanTest.RestoreArtifact(healthcheck) + session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(125)) + }) + + It("podman healthcheck on container without healthcheck", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(125)) + }) + + It("podman healthcheck should be starting", func() { + 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)) + inspect := podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("starting")) + }) + + It("podman healthcheck failed checks in start-period should not change status", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--healthcheck-start-period", "2m", "--healthcheck-retries", "2", "--healthcheck-command", "\"CMD-SHELL ls /foo || exit 1\"", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + inspect := podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("starting")) + }) + + It("podman healthcheck failed checks must reach retries before unhealthy ", func() { + 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)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + inspect := podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("starting")) + + hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + inspect = podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("unhealthy")) + + }) + + 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)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(0)) + + inspect := podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("healthy")) + }) + + 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)) + + hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + inspect := podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("starting")) + + hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(1)) + + inspect = podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("unhealthy")) + + foo := podmanTest.Podman([]string{"exec", "hc", "touch", "/foo"}) + foo.WaitWithDefaultTimeout() + Expect(foo.ExitCode()).To(BeZero()) + + hc = podmanTest.Podman([]string{"healthcheck", "run", "hc"}) + hc.WaitWithDefaultTimeout() + Expect(hc.ExitCode()).To(Equal(0)) + + inspect = podmanTest.InspectContainer("hc") + Expect(inspect[0].State.Healthcheck.Status).To(Equal("healthy")) + }) +}) diff --git a/test/e2e/history_test.go b/test/e2e/history_test.go index 9bec9ad13..9e519dd9c 100644 --- a/test/e2e/history_test.go +++ b/test/e2e/history_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,14 @@ var _ = Describe("Podman history", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 595084403..a253dff63 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -24,14 +24,14 @@ var _ = Describe("Podman images", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) It("podman images", func() { @@ -43,6 +43,15 @@ var _ = Describe("Podman images", func() { Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) }) + It("podman image List", func() { + session := podmanTest.Podman([]string{"image", "list"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) + Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue()) + Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue()) + }) + It("podman images with multiple tags", func() { // tag "docker.io/library/alpine:latest" to "foo:{a,b,c}" session := podmanTest.Podman([]string{"tag", ALPINE, "foo:a", "foo:b", "foo:c"}) @@ -103,6 +112,45 @@ var _ = Describe("Podman images", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(len(session.OutputToStringArray())).To(Equal(1)) + + session = podmanTest.Podman([]string{"tag", ALPINE, "foo:a"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"tag", BB, "foo:b"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"images", "-q", "foo"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(2)) + }) + + It("podman images filter reference", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } + result := podmanTest.Podman([]string{"images", "-q", "-f", "reference=docker.io*"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(2)) + + retapline := podmanTest.Podman([]string{"images", "-f", "reference=a*pine"}) + retapline.WaitWithDefaultTimeout() + Expect(retapline.ExitCode()).To(Equal(0)) + Expect(len(retapline.OutputToStringArray())).To(Equal(2)) + Expect(retapline.LineInOutputContains("alpine")) + + retapline = podmanTest.Podman([]string{"images", "-f", "reference=alpine"}) + retapline.WaitWithDefaultTimeout() + Expect(retapline.ExitCode()).To(Equal(0)) + Expect(len(retapline.OutputToStringArray())).To(Equal(2)) + Expect(retapline.LineInOutputContains("alpine")) + + retnone := podmanTest.Podman([]string{"images", "-q", "-f", "reference=bogus"}) + retnone.WaitWithDefaultTimeout() + Expect(retnone.ExitCode()).To(Equal(0)) + Expect(len(retnone.OutputToStringArray())).To(Equal(0)) }) It("podman images filter before image", func() { @@ -135,6 +183,23 @@ var _ = Describe("Podman images", func() { Expect(len(result.OutputToStringArray())).To(Equal(1)) }) + It("podman image list filter after image", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } + rmi := podmanTest.Podman([]string{"image", "rm", "busybox"}) + rmi.WaitWithDefaultTimeout() + Expect(rmi.ExitCode()).To(Equal(0)) + + dockerfile := `FROM docker.io/library/alpine:latest +` + podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false") + result := podmanTest.Podman([]string{"image", "list", "-q", "-f", "after=docker.io/library/alpine:latest"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + It("podman images filter dangling", func() { if podmanTest.RemoteTest { Skip("Does not work on remote client") @@ -164,6 +229,21 @@ var _ = Describe("Podman images", func() { Expect(result.ExitCode()).To(Equal(0)) }) + It("podman check for image with sha256: prefix", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } + session := podmanTest.Podman([]string{"image", "inspect", "--format=json", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.IsJSONOutputValid()).To(BeTrue()) + imageData := session.InspectImageJSON() + + result := podmanTest.Podman([]string{"image", "ls", fmt.Sprintf("sha256:%s", imageData[0].ID)}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) + It("podman images sort by tag", func() { session := podmanTest.Podman([]string{"images", "--sort", "tag", "--format={{.Tag}}"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/import_test.go b/test/e2e/import_test.go index dc7451f7b..e819d819c 100644 --- a/test/e2e/import_test.go +++ b/test/e2e/import_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "path/filepath" @@ -25,14 +24,15 @@ var _ = Describe("Podman import", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman import with source and reference", func() { diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go index a50c27dda..c960fb311 100644 --- a/test/e2e/info_test.go +++ b/test/e2e/info_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,13 +23,14 @@ var _ = Describe("Podman Info", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman info json output", func() { @@ -41,7 +41,7 @@ var _ = Describe("Podman Info", func() { }) It("podman system info json output", func() { session := podmanTest.Podman([]string{"system", "info", "--format=json"}) - session.Wait() + session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index e5c471bf9..34328828f 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" "strings" @@ -23,14 +22,15 @@ var _ = Describe("Podman inspect", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman inspect alpine image", func() { @@ -57,7 +57,7 @@ var _ = Describe("Podman inspect", func() { result := podmanTest.Podman([]string{"images", "-q", "--no-trunc", ALPINE}) result.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - Expect(strings.Trim(result.OutputToString(), "sha256:")).To(Equal(session.OutputToString())) + Expect(strings.Contains(result.OutputToString(), session.OutputToString())) }) It("podman inspect specified type", func() { @@ -66,6 +66,16 @@ var _ = Describe("Podman inspect", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman inspect container with GO format for ConmonPidFile", func() { + SkipIfRemote() + session, ec, _ := podmanTest.RunLsContainer("test1") + Expect(ec).To(Equal(0)) + + session = podmanTest.Podman([]string{"inspect", "--format", "{{.ConmonPidFile}}", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman inspect container with size", func() { SkipIfRemote() _, ec, _ := podmanTest.RunLsContainer("") diff --git a/test/e2e/kill_test.go b/test/e2e/kill_test.go index 5f1f5f4c1..618ca5aa0 100644 --- a/test/e2e/kill_test.go +++ b/test/e2e/kill_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,14 @@ var _ = Describe("Podman kill", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -41,6 +40,19 @@ var _ = Describe("Podman kill", func() { Expect(session.ExitCode()).To(Not(Equal(0))) }) + It("podman container kill a running container by id", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + result := podmanTest.Podman([]string{"container", "kill", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + }) + It("podman kill a running container by id", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() diff --git a/test/e2e/libpod_suite_remoteclient_test.go b/test/e2e/libpod_suite_remoteclient_test.go index e6bc00397..44c5edf07 100644 --- a/test/e2e/libpod_suite_remoteclient_test.go +++ b/test/e2e/libpod_suite_remoteclient_test.go @@ -18,6 +18,12 @@ func SkipIfRemote() { ginkgo.Skip("This function is not enabled for remote podman") } +func SkipIfRootless() { + if os.Geteuid() != 0 { + ginkgo.Skip("This function is not enabled for remote podman") + } +} + // Cleanup cleans up the temporary store func (p *PodmanTestIntegration) Cleanup() { p.StopVarlink() @@ -133,6 +139,9 @@ func (p *PodmanTestIntegration) CleanupVolume() { } func PodmanTestCreate(tempDir string) *PodmanTestIntegration { + if os.Geteuid() != 0 { + ginkgo.Skip("This function is not enabled for rootless podman") + } pti := PodmanTestCreateUtil(tempDir, true) pti.StartVarlink() return pti diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index 33e05b872..685a08340 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -14,12 +14,23 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" . "github.com/containers/libpod/test/utils" + "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" ) -func SkipIfRemote() {} +func SkipIfRemote() { + if os.Geteuid() != 0 { + ginkgo.Skip("This function is not enabled for rootless podman") + } +} + +func SkipIfRootless() { + if os.Geteuid() != 0 { + ginkgo.Skip("This function is not enabled for rootless podman") + } +} // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { @@ -206,8 +217,8 @@ func PodmanTestCreate(tempDir string) *PodmanTestIntegration { //MakeOptions assembles all the podman main options func (p *PodmanTestIntegration) makeOptions(args []string) []string { - podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s", - p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ") + podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s --tmpdir %s", + p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager, p.TmpDir), " ") if os.Getenv("HOOK_OPTION") != "" { podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION")) } diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 571754347..0e193640e 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "path/filepath" @@ -25,14 +24,15 @@ var _ = Describe("Podman load", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman load input flag", func() { @@ -190,7 +190,7 @@ var _ = Describe("Podman load", func() { load.WaitWithDefaultTimeout() Expect(load.ExitCode()).To(Equal(0)) - result := podmanTest.Podman([]string{"images", "-f", "label", "hello:world"}) + result := podmanTest.Podman([]string{"images", "hello:world"}) result.WaitWithDefaultTimeout() Expect(result.LineInOutputContains("docker")).To(Not(BeTrue())) Expect(result.LineInOutputContains("localhost")).To(BeTrue()) @@ -216,7 +216,7 @@ var _ = Describe("Podman load", func() { load.WaitWithDefaultTimeout() Expect(load.ExitCode()).To(Equal(0)) - result := podmanTest.Podman([]string{"images", "-f", "label", "hello:latest"}) + result := podmanTest.Podman([]string{"images", "hello:latest"}) result.WaitWithDefaultTimeout() Expect(result.LineInOutputContains("docker")).To(Not(BeTrue())) Expect(result.LineInOutputContains("localhost")).To(BeTrue()) @@ -241,7 +241,7 @@ var _ = Describe("Podman load", func() { load.WaitWithDefaultTimeout() Expect(load.ExitCode()).To(Equal(0)) - result := podmanTest.Podman([]string{"images", "-f", "label", "load:latest"}) + result := podmanTest.Podman([]string{"images", "load:latest"}) result.WaitWithDefaultTimeout() Expect(result.LineInOutputContains("docker")).To(Not(BeTrue())) Expect(result.LineInOutputContains("localhost")).To(BeTrue()) diff --git a/test/e2e/logs_test.go b/test/e2e/logs_test.go index d3c4fb802..d051e3dba 100644 --- a/test/e2e/logs_test.go +++ b/test/e2e/logs_test.go @@ -3,8 +3,8 @@ package integration import ( - "fmt" "os" + "strings" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -24,17 +24,17 @@ var _ = Describe("Podman logs", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) - //sudo bin/podman run -it --rm fedora-minimal bash -c 'for a in `seq 5`; do echo hello; done' It("podman logs for container", func() { logc := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) logc.WaitWithDefaultTimeout() @@ -106,4 +106,40 @@ var _ = Describe("Podman logs", func() { Expect(results.ExitCode()).To(Equal(0)) Expect(len(results.OutputToStringArray())).To(Equal(3)) }) + + It("podman logs latest and container name should fail", func() { + results := podmanTest.Podman([]string{"logs", "-l", "foobar"}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).ToNot(Equal(0)) + }) + + It("podman logs two containers and should display short container IDs", func() { + log1 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + log1.WaitWithDefaultTimeout() + Expect(log1.ExitCode()).To(Equal(0)) + cid1 := log1.OutputToString() + + log2 := podmanTest.Podman([]string{"run", "-dt", ALPINE, "sh", "-c", "echo podman; echo podman; echo podman"}) + log2.WaitWithDefaultTimeout() + Expect(log2.ExitCode()).To(Equal(0)) + cid2 := log2.OutputToString() + + results := podmanTest.Podman([]string{"logs", cid1, cid2}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(Equal(0)) + + output := results.OutputToStringArray() + Expect(len(output)).To(Equal(6)) + Expect(strings.Contains(output[0], cid1[:12]) || strings.Contains(output[0], cid2[:12])).To(BeTrue()) + }) + + It("podman logs on a created container should result in 0 exit code", func() { + session := podmanTest.Podman([]string{"create", "-dt", "--name", "log", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(BeZero()) + + results := podmanTest.Podman([]string{"logs", "log"}) + results.WaitWithDefaultTimeout() + Expect(results.ExitCode()).To(BeZero()) + }) }) diff --git a/test/e2e/mount_test.go b/test/e2e/mount_test.go index 94218e6a9..b361e0057 100644 --- a/test/e2e/mount_test.go +++ b/test/e2e/mount_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman mount", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman mount", func() { @@ -49,6 +49,21 @@ var _ = Describe("Podman mount", func() { Expect(umount.ExitCode()).To(Equal(0)) }) + It("podman container mount", func() { + setup := podmanTest.Podman([]string{"container", "create", ALPINE, "ls"}) + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + mount := podmanTest.Podman([]string{"container", "mount", cid}) + mount.WaitWithDefaultTimeout() + Expect(mount.ExitCode()).To(Equal(0)) + + umount := podmanTest.Podman([]string{"container", "umount", cid}) + umount.WaitWithDefaultTimeout() + Expect(umount.ExitCode()).To(Equal(0)) + }) + It("podman mount with json format", func() { setup := podmanTest.Podman([]string{"create", ALPINE, "ls"}) setup.WaitWithDefaultTimeout() diff --git a/test/e2e/namespace_test.go b/test/e2e/namespace_test.go index a0b6e6187..28d050be3 100644 --- a/test/e2e/namespace_test.go +++ b/test/e2e/namespace_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman namespaces", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman namespace test", func() { diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index f1ea17ead..e28c31c3a 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -22,19 +22,21 @@ var _ = Describe("Podman pause", func() { createdState := "Created" BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pause bogus container", func() { @@ -80,6 +82,23 @@ var _ = Describe("Podman pause", func() { result.WaitWithDefaultTimeout() }) + It("podman container pause a running container by id", func() { + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + result := podmanTest.Podman([]string{"container", "pause", cid}) + result.WaitWithDefaultTimeout() + + Expect(result.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0)) + Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState)) + + result = podmanTest.Podman([]string{"container", "unpause", cid}) + result.WaitWithDefaultTimeout() + }) + It("podman unpause a running container by id", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_create_test.go b/test/e2e/pod_create_test.go index 4717267a1..de0734e9f 100644 --- a/test/e2e/pod_create_test.go +++ b/test/e2e/pod_create_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod create", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman create pod", func() { @@ -100,7 +100,7 @@ var _ = Describe("Podman pod create", func() { It("podman create pod with network portbindings", func() { name := "test" - session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"}) + session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "8080:80"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) pod := session.OutputToString() @@ -109,7 +109,7 @@ var _ = Describe("Podman pod create", func() { webserver.WaitWithDefaultTimeout() Expect(webserver.ExitCode()).To(Equal(0)) - check := SystemExec("nc", []string{"-z", "localhost", "80"}) + check := SystemExec("nc", []string{"-z", "localhost", "8080"}) Expect(check.ExitCode()).To(Equal(0)) }) diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go index ed5002ca7..82f35999c 100644 --- a/test/e2e/pod_infra_container_test.go +++ b/test/e2e/pod_infra_container_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "strconv" @@ -25,6 +24,7 @@ var _ = Describe("Podman pod create", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() podmanTest.RestoreArtifact(infra) }) @@ -32,8 +32,8 @@ var _ = Describe("Podman pod create", func() { AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman create infra container", func() { @@ -69,6 +69,18 @@ var _ = Describe("Podman pod create", func() { Expect(len(check.OutputToStringArray())).To(Equal(1)) }) + It("podman start infra container different image", func() { + session := podmanTest.Podman([]string{"pod", "create", "--infra-image", BB}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podID := session.OutputToString() + + session = podmanTest.Podman([]string{"pod", "start", podID}) + session.WaitWithDefaultTimeout() + // If we use the default entry point, we should exit with no error + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman infra container namespaces", func() { session := podmanTest.Podman([]string{"pod", "create"}) session.WaitWithDefaultTimeout() @@ -209,8 +221,7 @@ var _ = Describe("Podman pod create", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - podmanTest.RestoreArtifact(fedoraMinimal) - session = podmanTest.Podman([]string{"run", "--pod", podID, "--network", "bridge", fedoraMinimal, "curl", "localhost"}) + session = podmanTest.Podman([]string{"run", "--pod", podID, "--network", "bridge", nginx, "curl", "localhost"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) }) @@ -360,4 +371,21 @@ var _ = Describe("Podman pod create", func() { Expect(result.OutputToString()).To(ContainSubstring(infraID)) }) + + It("podman run --add-host in pod", func() { + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + podID := session.OutputToString() + + // verify we can add a host to the infra's /etc/hosts + session = podmanTest.Podman([]string{"run", "--pod", podID, "--add-host", "foobar:127.0.0.1", BB, "ping", "-c", "1", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + // verify we can see the other hosts of infra's /etc/hosts + session = podmanTest.Podman([]string{"run", "--pod", podID, BB, "ping", "-c", "1", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go index 457acb373..671d203a6 100644 --- a/test/e2e/pod_inspect_test.go +++ b/test/e2e/pod_inspect_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod inspect", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman inspect bogus pod", func() { diff --git a/test/e2e/pod_kill_test.go b/test/e2e/pod_kill_test.go index 419a3a777..c1f7503e3 100644 --- a/test/e2e/pod_kill_test.go +++ b/test/e2e/pod_kill_test.go @@ -24,14 +24,15 @@ var _ = Describe("Podman pod kill", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod kill bogus", func() { diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go index a5192f84b..59a4da176 100644 --- a/test/e2e/pod_pause_test.go +++ b/test/e2e/pod_pause_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -21,19 +20,21 @@ var _ = Describe("Podman pod pause", func() { pausedState := "Paused" BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod pause bogus pod", func() { diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go index 9815e37ef..9d6321c0e 100644 --- a/test/e2e/pod_pod_namespaces.go +++ b/test/e2e/pod_pod_namespaces.go @@ -24,6 +24,7 @@ var _ = Describe("Podman pod create", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() podmanTest.RestoreArtifact(infra) }) @@ -31,8 +32,8 @@ var _ = Describe("Podman pod create", func() { AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod container share Namespaces", func() { diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go index 3b7198861..2fa26d7ad 100644 --- a/test/e2e/pod_ps_test.go +++ b/test/e2e/pod_ps_test.go @@ -25,14 +25,15 @@ var _ = Describe("Podman ps", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod ps no pods", func() { diff --git a/test/e2e/pod_restart_test.go b/test/e2e/pod_restart_test.go index e8acfd2ec..ffb6cb94c 100644 --- a/test/e2e/pod_restart_test.go +++ b/test/e2e/pod_restart_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod restart", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod restart bogus pod", func() { diff --git a/test/e2e/pod_rm_test.go b/test/e2e/pod_rm_test.go index f63d2c8aa..f9d7abe8f 100644 --- a/test/e2e/pod_rm_test.go +++ b/test/e2e/pod_rm_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod rm", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod rm empty pod", func() { diff --git a/test/e2e/pod_start_test.go b/test/e2e/pod_start_test.go index ce693012d..de52af2a0 100644 --- a/test/e2e/pod_start_test.go +++ b/test/e2e/pod_start_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod start", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod start bogus pod", func() { diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index e330c3a39..6018b4494 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -19,19 +18,21 @@ var _ = Describe("Podman pod stats", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman stats should run with no pods", func() { session := podmanTest.Podman([]string{"pod", "stats", "--no-stream"}) diff --git a/test/e2e/pod_stop_test.go b/test/e2e/pod_stop_test.go index 38f118964..fa285fa80 100644 --- a/test/e2e/pod_stop_test.go +++ b/test/e2e/pod_stop_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman pod stop", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod stop bogus pod", func() { diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go index 507d723b4..964ee075f 100644 --- a/test/e2e/pod_top_test.go +++ b/test/e2e/pod_top_test.go @@ -5,6 +5,7 @@ package integration import ( "fmt" "os" + "time" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" @@ -24,14 +25,15 @@ var _ = Describe("Podman top", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupPod() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pod top without pod name or id", func() { @@ -127,6 +129,13 @@ var _ = Describe("Podman top", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) + for i := 0; i < 10; i++ { + fmt.Println("Waiting for containers to be running .... ") + if podmanTest.NumberOfContainersRunning() == 2 { + break + } + time.Sleep(1 * time.Second) + } result := podmanTest.Podman([]string{"pod", "top", podid}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go index fa633c379..7cf3e16bf 100644 --- a/test/e2e/port_test.go +++ b/test/e2e/port_test.go @@ -25,14 +25,15 @@ var _ = Describe("Podman port", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman port all and latest", func() { @@ -60,6 +61,19 @@ var _ = Describe("Podman port", func() { Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) }) + It("podman container port -l nginx", func() { + podmanTest.RestoreArtifact(nginx) + session := podmanTest.Podman([]string{"container", "run", "-dt", "-P", nginx}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "port", "-l"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + port := strings.Split(result.OutputToStringArray()[0], ":")[1] + Expect(result.LineInOuputStartsWith(fmt.Sprintf("80/tcp -> 0.0.0.0:%s", port))).To(BeTrue()) + }) + It("podman port -l port nginx", func() { podmanTest.RestoreArtifact(nginx) session := podmanTest.Podman([]string{"run", "-dt", "-P", nginx}) diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 74cdc126f..869ca3289 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -28,14 +27,15 @@ var _ = Describe("Podman rm", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman container prune containers", func() { diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go index 9b1c55bb4..957c69aa8 100644 --- a/test/e2e/ps_test.go +++ b/test/e2e/ps_test.go @@ -28,14 +28,15 @@ var _ = Describe("Podman ps", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman ps no containers", func() { @@ -65,7 +66,24 @@ var _ = Describe("Podman ps", func() { Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0)) }) + It("podman container list all", func() { + _, ec, _ := podmanTest.RunLsContainer("") + Expect(ec).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "list", "-a"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0)) + + result = podmanTest.Podman([]string{"container", "ls", "-a"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).Should(BeNumerically(">", 0)) + }) + It("podman ps size flag", func() { + SkipIfRootless() + _, ec, _ := podmanTest.RunLsContainer("") Expect(ec).To(Equal(0)) @@ -217,6 +235,8 @@ var _ = Describe("Podman ps", func() { }) It("podman --sort by size", func() { + SkipIfRootless() + session := podmanTest.Podman([]string{"create", "busybox", "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -256,8 +276,7 @@ var _ = Describe("Podman ps", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - podmanTest.RestoreArtifact(fedoraMinimal) - session = podmanTest.Podman([]string{"run", "-d", fedoraMinimal, "pwd"}) + session = podmanTest.Podman([]string{"run", "-d", ALPINE, "pwd"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -288,4 +307,19 @@ var _ = Describe("Podman ps", func() { Expect(session.OutputToString()).To(ContainSubstring(podid)) }) + + It("podman ps test with port range", func() { + SkipIfRootless() + session := podmanTest.RunTopContainer("") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"run", "-dt", "-p", "1000-1006:1000-1006", ALPINE, "top"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"ps", "--format", "{{.Ports}}"}) + session.WaitWithDefaultTimeout() + Expect(session.OutputToString()).To(ContainSubstring("0.0.0.0:1000-1006")) + }) }) diff --git a/test/e2e/pull_test.go b/test/e2e/pull_test.go index d9b9c7213..de6d4ea09 100644 --- a/test/e2e/pull_test.go +++ b/test/e2e/pull_test.go @@ -27,14 +27,15 @@ var _ = Describe("Podman pull", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman pull from docker with tag", func() { diff --git a/test/e2e/push_test.go b/test/e2e/push_test.go index fee117783..009067482 100644 --- a/test/e2e/push_test.go +++ b/test/e2e/push_test.go @@ -26,14 +26,15 @@ var _ = Describe("Podman push", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman push to containers/storage", func() { @@ -62,6 +63,8 @@ var _ = Describe("Podman push", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) session.WaitWithDefaultTimeout() @@ -77,6 +80,7 @@ var _ = Describe("Podman push", func() { }) It("podman push to local registry with authorization", func() { + SkipIfRootless() if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } @@ -100,6 +104,8 @@ var _ = Describe("Podman push", func() { }() } } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) session := podmanTest.Podman([]string{"run", "--entrypoint", "htpasswd", registry, "-Bbn", "podmantest", "test"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/refresh_test.go b/test/e2e/refresh_test.go index de331bf88..56c1d255e 100644 --- a/test/e2e/refresh_test.go +++ b/test/e2e/refresh_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "time" @@ -25,14 +24,15 @@ var _ = Describe("Podman refresh", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tmpdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) Specify("Refresh with no containers succeeds", func() { diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 5c914a367..1daf63a0e 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "time" @@ -25,14 +24,15 @@ var _ = Describe("Podman restart", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("Podman restart bogus container", func() { @@ -90,6 +90,21 @@ var _ = Describe("Podman restart", func() { Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) }) + It("Podman container restart running container", func() { + _ = podmanTest.RunTopContainer("test1") + ok := WaitForContainer(podmanTest) + Expect(ok).To(BeTrue()) + startTime := podmanTest.Podman([]string{"container", "inspect", "--format='{{.State.StartedAt}}'", "test1"}) + startTime.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"container", "restart", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + restartTime := podmanTest.Podman([]string{"container", "inspect", "--format='{{.State.StartedAt}}'", "test1"}) + restartTime.WaitWithDefaultTimeout() + Expect(restartTime.OutputToString()).To(Not(Equal(startTime.OutputToString()))) + }) + It("Podman restart multiple containers", func() { _, exitCode, _ := podmanTest.RunLsContainer("test1") Expect(exitCode).To(Equal(0)) diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go index 71dacfa80..9bf742a63 100644 --- a/test/e2e/rm_test.go +++ b/test/e2e/rm_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman rm", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman rm stopped container", func() { @@ -65,6 +65,17 @@ var _ = Describe("Podman rm", func() { Expect(result.ExitCode()).To(Equal(0)) }) + It("podman container rm created container", func() { + session := podmanTest.Podman([]string{"container", "create", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + + result := podmanTest.Podman([]string{"container", "rm", cid}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) + It("podman rm running container with -f", func() { session := podmanTest.RunTopContainer("") session.WaitWithDefaultTimeout() @@ -128,9 +139,23 @@ var _ = Describe("Podman rm", func() { Expect(podmanTest.NumberOfContainers()).To(Equal(1)) }) + It("podman rm bogus container", func() { session := podmanTest.Podman([]string{"rm", "bogus"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(1)) }) + It("podman rm bogus container and a running container", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"rm", "bogus", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + + session = podmanTest.Podman([]string{"rm", "test1", "bogus"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + }) }) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index dcbda2df4..78d175637 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -22,14 +22,14 @@ var _ = Describe("Podman rmi", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) @@ -275,4 +275,13 @@ RUN find $LOCAL Expect(images.ExitCode()).To(Equal(0)) Expect(len(images.OutputToStringArray())).To(Equal(0)) }) + + // Don't rerun all tests; just assume that if we get that diagnostic, + // we're getting rmi + It("podman image rm is the same as rmi", func() { + session := podmanTest.Podman([]string{"image", "rm"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(125)) + Expect(session.LineInOutputContains("image name or ID must be specified")) + }) }) diff --git a/test/e2e/rootless_test.go b/test/e2e/rootless_test.go index aa8ed6faa..51544ff8b 100644 --- a/test/e2e/rootless_test.go +++ b/test/e2e/rootless_test.go @@ -38,6 +38,7 @@ var _ = Describe("Podman rootless", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) @@ -45,14 +46,15 @@ var _ = Describe("Podman rootless", func() { podmanTest = PodmanTestCreate(tempdir) podmanTest.CgroupManager = "cgroupfs" podmanTest.StorageOptions = ROOTLESS_STORAGE_OPTIONS + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman rootless help|version", func() { @@ -125,7 +127,6 @@ var _ = Describe("Podman rootless", func() { env := os.Environ() env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) env = append(env, fmt.Sprintf("HOME=%s", home)) - env = append(env, "PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS=1") env = append(env, "USER=foo") cmd := rootlessTest.PodmanAsUser([]string{"pod", "create", "--infra=false"}, 1000, 1000, "", env) @@ -138,6 +139,21 @@ var _ = Describe("Podman rootless", func() { cmd.WaitWithDefaultTimeout() Expect(cmd.ExitCode()).To(Equal(0)) Expect(cmd.LineInOutputContains("hello")).To(BeTrue()) + + args = []string{"pod", "top", podId} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Not(Equal(0))) + + args = []string{"run", "--pod", podId, "-d", "--rootfs", mountPath, "sleep", "100"} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) + + args = []string{"pod", "top", podId} + cmd = rootlessTest.PodmanAsUser(args, 1000, 1000, "", env) + cmd.WaitWithDefaultTimeout() + Expect(cmd.ExitCode()).To(Equal(0)) } runInRootlessContext(f) }) @@ -170,7 +186,6 @@ var _ = Describe("Podman rootless", func() { env := os.Environ() env = append(env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", xdgRuntimeDir)) env = append(env, fmt.Sprintf("HOME=%s", home)) - env = append(env, "PODMAN_ALLOW_SINGLE_ID_MAPPING_IN_USERNS=1") env = append(env, "USER=foo") allArgs := append([]string{"run"}, args...) diff --git a/test/e2e/run_cgroup_parent_test.go b/test/e2e/run_cgroup_parent_test.go index efc9a7009..0d04c5f03 100644 --- a/test/e2e/run_cgroup_parent_test.go +++ b/test/e2e/run_cgroup_parent_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -19,19 +18,21 @@ var _ = Describe("Podman run with --cgroup-parent", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreArtifact(fedoraMinimal) }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) Specify("valid --cgroup-parent using cgroupfs", func() { diff --git a/test/e2e/run_cleanup_test.go b/test/e2e/run_cleanup_test.go index 1f2a4085d..b20e37794 100644 --- a/test/e2e/run_cleanup_test.go +++ b/test/e2e/run_cleanup_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run exit", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run -d mount cleanup test", func() { diff --git a/test/e2e/run_cpu_test.go b/test/e2e/run_cpu_test.go index f74d3ed84..42a66865c 100644 --- a/test/e2e/run_cpu_test.go +++ b/test/e2e/run_cpu_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,17 +23,19 @@ var _ = Describe("Podman run cpu", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run cpu-period", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpu-period=5000", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_period_us"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -42,6 +43,7 @@ var _ = Describe("Podman run cpu", func() { }) It("podman run cpu-quota", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpu-quota=5000", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -49,6 +51,7 @@ var _ = Describe("Podman run cpu", func() { }) It("podman run cpus", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpus=0.5", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_period_us"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -61,6 +64,7 @@ var _ = Describe("Podman run cpu", func() { }) It("podman run cpu-shares", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpu-shares=2", ALPINE, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -68,6 +72,7 @@ var _ = Describe("Podman run cpu", func() { }) It("podman run cpuset-cpus", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpuset-cpus=0", ALPINE, "cat", "/sys/fs/cgroup/cpuset/cpuset.cpus"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) @@ -75,6 +80,7 @@ var _ = Describe("Podman run cpu", func() { }) It("podman run cpuset-mems", func() { + SkipIfRootless() result := podmanTest.Podman([]string{"run", "--rm", "--cpuset-mems=0", ALPINE, "cat", "/sys/fs/cgroup/cpuset/cpuset.mems"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go index 4f26ac8ee..fac09b78d 100644 --- a/test/e2e/run_device_test.go +++ b/test/e2e/run_device_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run device", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run bad device test", func() { @@ -41,6 +41,7 @@ var _ = Describe("Podman run device", func() { }) It("podman run device test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg", ALPINE, "ls", "--color=never", "/dev/kmsg"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -48,6 +49,7 @@ var _ = Describe("Podman run device", func() { }) It("podman run device rename test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1", ALPINE, "ls", "--color=never", "/dev/kmsg1"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -55,6 +57,7 @@ var _ = Describe("Podman run device", func() { }) It("podman run device permission test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:r", ALPINE, "ls", "--color=never", "/dev/kmsg"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -62,6 +65,7 @@ var _ = Describe("Podman run device", func() { }) It("podman run device rename and permission test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/kmsg:/dev/kmsg1:r", ALPINE, "ls", "--color=never", "/dev/kmsg1"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -72,4 +76,13 @@ var _ = Describe("Podman run device", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Not(Equal(0))) }) + + It("podman run device host device and container device parameter are directories", func() { + SkipIfRootless() + SystemExec("mkdir", []string{"/dev/foodevdir"}) + SystemExec("mknod", []string{"/dev/foodevdir/null", "c", "1", "3"}) + session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/foodevdir:/dev/bar", ALPINE, "ls", "/dev/bar/null"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/run_dns_test.go b/test/e2e/run_dns_test.go index 6c649cdbc..0f4dd6742 100644 --- a/test/e2e/run_dns_test.go +++ b/test/e2e/run_dns_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run dns", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run add search domain", func() { @@ -88,6 +88,7 @@ var _ = Describe("Podman run dns", func() { }) It("podman run add hostname sets /etc/hosts", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-t", "-i", "--hostname=foobar", ALPINE, "cat", "/etc/hosts"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go index a33e16b63..ee9fd1263 100644 --- a/test/e2e/run_entrypoint_test.go +++ b/test/e2e/run_entrypoint_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run entrypoint", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreArtifact(ALPINE) }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run no command, entrypoint, or cmd", func() { diff --git a/test/e2e/run_exit_test.go b/test/e2e/run_exit_test.go index 03072f598..da4cf7ee7 100644 --- a/test/e2e/run_exit_test.go +++ b/test/e2e/run_exit_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run exit", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run exit 125", func() { @@ -59,8 +59,7 @@ var _ = Describe("Podman run exit", func() { }) It("podman run exit 50", func() { - podmanTest.RestoreArtifact(fedoraMinimal) - result := podmanTest.Podman([]string{"run", "registry.fedoraproject.org/fedora-minimal", "bash", "-c", "exit 50"}) + result := podmanTest.Podman([]string{"run", ALPINE, "sh", "-c", "exit 50"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(50)) }) diff --git a/test/e2e/run_memory_test.go b/test/e2e/run_memory_test.go index e9262d4f0..05d0b7a18 100644 --- a/test/e2e/run_memory_test.go +++ b/test/e2e/run_memory_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -19,19 +18,21 @@ var _ = Describe("Podman run memory", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run memory test", func() { diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index c89a4f487..93919925c 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -25,14 +24,15 @@ var _ = Describe("Podman run networking", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run network connection with default bridge", func() { @@ -54,6 +54,7 @@ var _ = Describe("Podman run networking", func() { }) It("podman run network expose port 222", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "--expose", "222-223", "-P", ALPINE, "/bin/sh"}) session.Wait(30) Expect(session.ExitCode()).To(Equal(0)) @@ -64,6 +65,7 @@ var _ = Describe("Podman run networking", func() { }) It("podman run network expose host port 80 to container port 8000", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"}) session.Wait(30) Expect(session.ExitCode()).To(Equal(0)) @@ -146,6 +148,7 @@ var _ = Describe("Podman run networking", func() { }) It("podman run --net container: copies hosts and resolv", func() { + SkipIfRootless() ctrName := "ctr1" ctr1 := podmanTest.RunTopContainer(ctrName) ctr1.WaitWithDefaultTimeout() @@ -177,6 +180,7 @@ var _ = Describe("Podman run networking", func() { }) It("podman run network in user created network namespace", func() { + SkipIfRootless() if Containerized() { Skip("Can not be run within a container.") } @@ -193,6 +197,7 @@ var _ = Describe("Podman run networking", func() { }) It("podman run n user created network namespace with resolv.conf", func() { + SkipIfRootless() if Containerized() { Skip("Can not be run within a container.") } diff --git a/test/e2e/run_ns_test.go b/test/e2e/run_ns_test.go index 3d95c3a0b..5236e6584 100644 --- a/test/e2e/run_ns_test.go +++ b/test/e2e/run_ns_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "strings" @@ -25,14 +24,15 @@ var _ = Describe("Podman run ns", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreArtifact(fedoraMinimal) }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run pidns test", func() { @@ -63,6 +63,7 @@ var _ = Describe("Podman run ns", func() { }) It("podman run ipcns ipcmk host test", func() { + SkipIfRootless() setup := SystemExec("ipcmk", []string{"-M", "1024"}) Expect(setup.ExitCode()).To(Equal(0)) output := strings.Split(setup.OutputToString(), " ") @@ -76,6 +77,7 @@ var _ = Describe("Podman run ns", func() { }) It("podman run ipcns ipcmk container test", func() { + SkipIfRootless() setup := podmanTest.Podman([]string{"run", "-d", "--name", "test1", fedoraMinimal, "sleep", "999"}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index fcb81fb77..becbc5bfa 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -5,7 +5,6 @@ package integration import ( "os" - "fmt" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -24,14 +23,15 @@ var _ = Describe("Podman run passwd", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run no user specified ", func() { diff --git a/test/e2e/run_privileged_test.go b/test/e2e/run_privileged_test.go index ee6e8e950..16011b2fd 100644 --- a/test/e2e/run_privileged_test.go +++ b/test/e2e/run_privileged_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "strings" @@ -25,14 +24,15 @@ var _ = Describe("Podman privileged container tests", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman privileged make sure sys is mounted rw", func() { @@ -45,6 +45,7 @@ var _ = Describe("Podman privileged container tests", func() { }) It("podman privileged CapEff", func() { + SkipIfRootless() cap := SystemExec("grep", []string{"CapEff", "/proc/self/status"}) Expect(cap.ExitCode()).To(Equal(0)) @@ -55,6 +56,7 @@ var _ = Describe("Podman privileged container tests", func() { }) It("podman cap-add CapEff", func() { + SkipIfRootless() cap := SystemExec("grep", []string{"CapEff", "/proc/self/status"}) Expect(cap.ExitCode()).To(Equal(0)) @@ -80,6 +82,7 @@ var _ = Describe("Podman privileged container tests", func() { }) It("podman privileged should inherit host devices", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--privileged", ALPINE, "ls", "-l", "/dev"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_restart_test.go b/test/e2e/run_restart_test.go index 2659d2b11..9976b45e8 100644 --- a/test/e2e/run_restart_test.go +++ b/test/e2e/run_restart_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman run restart containers", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("Podman start after successful run", func() { diff --git a/test/e2e/run_selinux_test.go b/test/e2e/run_selinux_test.go index 57e488abc..4d2bad49c 100644 --- a/test/e2e/run_selinux_test.go +++ b/test/e2e/run_selinux_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -25,6 +24,7 @@ var _ = Describe("Podman run", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() if !selinux.GetEnabled() { Skip("SELinux not enabled") @@ -34,8 +34,8 @@ var _ = Describe("Podman run", func() { AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run selinux", func() { @@ -112,6 +112,7 @@ var _ = Describe("Podman run", func() { }) It("podman test selinux label /run/secrets", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", fedoraMinimal, "ls", "-dZ", "/run/secrets"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -144,6 +145,7 @@ var _ = Describe("Podman run", func() { }) It("podman test selinux --privileged label /run/secrets", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--privileged", fedoraMinimal, "ls", "-dZ", "/run/secrets"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/run_signal_test.go b/test/e2e/run_signal_test.go index 51c14602e..e482adb84 100644 --- a/test/e2e/run_signal_test.go +++ b/test/e2e/run_signal_test.go @@ -32,14 +32,15 @@ var _ = Describe("Podman run with --sig-proxy", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tmpdir) + podmanTest.Setup() podmanTest.RestoreArtifact(fedoraMinimal) }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) Specify("signals are forwarded to container using sig-proxy", func() { diff --git a/test/e2e/run_staticip_test.go b/test/e2e/run_staticip_test.go index bf50e5eb7..318a7a62d 100644 --- a/test/e2e/run_staticip_test.go +++ b/test/e2e/run_staticip_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -19,11 +18,13 @@ var _ = Describe("Podman run with --ip flag", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() // Cleanup the CNI networks used by the tests os.RemoveAll("/var/lib/cni/networks/podman") @@ -32,8 +33,8 @@ var _ = Describe("Podman run with --ip flag", func() { AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("Podman run --ip with garbage address", func() { diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go index 93ee5036f..b0dc66707 100644 --- a/test/e2e/run_test.go +++ b/test/e2e/run_test.go @@ -29,14 +29,15 @@ var _ = Describe("Podman run", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman run a container based on local image", func() { @@ -46,6 +47,7 @@ var _ = Describe("Podman run", func() { }) It("podman run a container based on a complex local image name", func() { + SkipIfRootless() imageName := strings.TrimPrefix(nginx, "quay.io/") podmanTest.RestoreArtifact(nginx) session := podmanTest.Podman([]string{"run", imageName, "ls"}) @@ -68,6 +70,20 @@ var _ = Describe("Podman run", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman container run a container based on on a short name with localhost", func() { + podmanTest.RestoreArtifact(nginx) + tag := podmanTest.Podman([]string{"image", "tag", nginx, "localhost/libpod/alpine_nginx:latest"}) + tag.WaitWithDefaultTimeout() + + rmi := podmanTest.Podman([]string{"image", "rm", nginx}) + rmi.WaitWithDefaultTimeout() + + session := podmanTest.Podman([]string{"container", "run", "libpod/alpine_nginx:latest", "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ErrorToString()).ToNot(ContainSubstring("Trying to pull")) + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman run a container based on local image with short options", func() { session := podmanTest.Podman([]string{"run", "-dt", ALPINE, "ls"}) session.WaitWithDefaultTimeout() @@ -136,10 +152,10 @@ var _ = Describe("Podman run", func() { }) It("podman run environment test", func() { - session := podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR", ALPINE, "printenv", "FOO"}) + session := podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR,BAZ", ALPINE, "printenv", "FOO"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) - match, _ := session.GrepString("BAR") + match, _ := session.GrepString("BAR,BAZ") Expect(match).Should(BeTrue()) session = podmanTest.Podman([]string{"run", "--rm", "--env", "PATH=/bin", ALPINE, "printenv", "PATH"}) @@ -170,6 +186,7 @@ var _ = Describe("Podman run", func() { }) It("podman run limits test", func() { + SkipIfRootless() podmanTest.RestoreArtifact(fedoraMinimal) session := podmanTest.Podman([]string{"run", "--rm", "--ulimit", "rtprio=99", "--cap-add=sys_nice", fedoraMinimal, "cat", "/proc/self/sched"}) session.WaitWithDefaultTimeout() @@ -196,6 +213,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with volume flag", func() { + SkipIfRootless() Skip("Skip until we diagnose the regression of volume mounts") mountPath := filepath.Join(podmanTest.TempDir, "secrets") os.Mkdir(mountPath, 0755) @@ -260,6 +278,7 @@ var _ = Describe("Podman run", func() { }) It("podman run sysctl test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--sysctl", "net.core.somaxconn=65535", ALPINE, "sysctl", "net.core.somaxconn"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -267,6 +286,7 @@ var _ = Describe("Podman run", func() { }) It("podman run blkio-weight test", func() { + SkipIfRootless() if _, err := os.Stat("/sys/fs/cgroup/blkio/blkio.weight"); os.IsNotExist(err) { Skip("Kernel does not support blkio.weight") } @@ -277,6 +297,7 @@ var _ = Describe("Podman run", func() { }) It("podman run device-read-bps test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--device-read-bps=/dev/zero:1mb", ALPINE, "cat", "/sys/fs/cgroup/blkio/blkio.throttle.read_bps_device"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -284,6 +305,7 @@ var _ = Describe("Podman run", func() { }) It("podman run device-write-bps test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--device-write-bps=/dev/zero:1mb", ALPINE, "cat", "/sys/fs/cgroup/blkio/blkio.throttle.write_bps_device"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -291,6 +313,7 @@ var _ = Describe("Podman run", func() { }) It("podman run device-read-iops test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--device-read-iops=/dev/zero:100", ALPINE, "cat", "/sys/fs/cgroup/blkio/blkio.throttle.read_iops_device"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -298,6 +321,7 @@ var _ = Describe("Podman run", func() { }) It("podman run device-write-iops test", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--device-write-iops=/dev/zero:100", ALPINE, "cat", "/sys/fs/cgroup/blkio/blkio.throttle.write_iops_device"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -401,6 +425,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with FIPS mode secrets", func() { + SkipIfRootless() fipsFile := "/etc/system-fips" err = ioutil.WriteFile(fipsFile, []byte{}, 0755) Expect(err).To(BeNil()) @@ -415,6 +440,7 @@ var _ = Describe("Podman run", func() { }) It("podman run without group-add", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -422,6 +448,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with group-add", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--group-add=audio", "--group-add=nogroup", "--group-add=777", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -429,6 +456,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with user (default)", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -443,6 +471,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with user (integer, in /etc/passwd)", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--user=8", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -450,6 +479,7 @@ var _ = Describe("Podman run", func() { }) It("podman run with user (username)", func() { + SkipIfRootless() session := podmanTest.Podman([]string{"run", "--rm", "--user=mail", ALPINE, "id"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -704,4 +734,28 @@ USER mail` Expect(session.OutputToString()).To(Not(ContainSubstring("/dev/shm type tmpfs (ro,"))) }) + + It("podman run with bad healthcheck interval", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-interval", "0.5s", ALPINE, "top"}) + session.Wait() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) + + It("podman run with bad healthcheck retries", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-retries", "0", ALPINE, "top"}) + session.Wait() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) + + It("podman run with bad healthcheck timeout", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-timeout", "0s", ALPINE, "top"}) + session.Wait() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) + + It("podman run with bad healthcheck start-period", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--healthcheck-cmd", "foo", "--healthcheck-start-period", "-1s", ALPINE, "top"}) + session.Wait() + Expect(session.ExitCode()).ToNot(Equal(0)) + }) }) diff --git a/test/e2e/run_userns_test.go b/test/e2e/run_userns_test.go index 254897e70..5c38a8950 100644 --- a/test/e2e/run_userns_test.go +++ b/test/e2e/run_userns_test.go @@ -5,7 +5,6 @@ package integration import ( "os" - "fmt" . "github.com/containers/libpod/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -19,19 +18,21 @@ var _ = Describe("Podman UserNS support", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman uidmapping and gidmapping", func() { @@ -68,6 +69,21 @@ var _ = Describe("Podman UserNS support", func() { Expect(ok).To(BeTrue()) }) + It("podman uidmapping and gidmapping with a volume", func() { + if os.Getenv("SKIP_USERNS") != "" { + Skip("Skip userns tests.") + } + if _, err := os.Stat("/proc/self/uid_map"); err != nil { + Skip("User namespaces not supported.") + } + + session := podmanTest.Podman([]string{"run", "--uidmap=0:1:70000", "--gidmap=0:20000:70000", "-v", "my-foo-volume:/foo:Z", "busybox", "echo", "hello"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + ok, _ := session.GrepString("hello") + Expect(ok).To(BeTrue()) + }) + It("podman uidmapping and gidmapping --net=host", func() { if os.Getenv("SKIP_USERNS") != "" { Skip("Skip userns tests.") diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go index 49b9e13d8..b1d057bfd 100644 --- a/test/e2e/runlabel_test.go +++ b/test/e2e/runlabel_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -32,14 +31,14 @@ var _ = Describe("podman container runlabel", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) }) diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go index 9f64e49a7..c3edc7c7e 100644 --- a/test/e2e/save_test.go +++ b/test/e2e/save_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" "path/filepath" @@ -23,14 +22,15 @@ var _ = Describe("Podman save", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman save output flag", func() { diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 167f8fa25..589389b3b 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" "strconv" @@ -44,14 +43,15 @@ var _ = Describe("Podman search", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman search", func() { @@ -134,6 +134,9 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() + podmanTest.RestoreArtifact(registry) fakereg := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) fakereg.WaitWithDefaultTimeout() @@ -157,6 +160,8 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry3", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) registry.WaitWithDefaultTimeout() @@ -180,6 +185,8 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "--name", "registry4", "-p", "5000:5000", registry, "/entrypoint.sh", "/etc/docker/registry/config.yml"}) registry.WaitWithDefaultTimeout() @@ -212,6 +219,8 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry5", registry}) registry.WaitWithDefaultTimeout() @@ -243,6 +252,8 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) registry := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry6", registry}) registry.WaitWithDefaultTimeout() @@ -274,6 +285,8 @@ var _ = Describe("Podman search", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("No registry image for ppc64le") } + lock := GetPortLock("5000") + defer lock.Unlock() podmanTest.RestoreArtifact(registry) registryLocal := podmanTest.Podman([]string{"run", "-d", "-p", "5000:5000", "--name", "registry7", registry}) registryLocal.WaitWithDefaultTimeout() diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index c4ed6f545..28f1c2393 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman start", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman start bogus container", func() { @@ -50,6 +50,16 @@ var _ = Describe("Podman start", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman container start single container by id", func() { + session := podmanTest.Podman([]string{"container", "create", "-d", ALPINE, "ls"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid := session.OutputToString() + session = podmanTest.Podman([]string{"container", "start", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman start single container by name", func() { session := podmanTest.Podman([]string{"create", "-d", "--name", "foobar99", ALPINE, "ls"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go index e7b0b5f6e..05f24539f 100644 --- a/test/e2e/stats_test.go +++ b/test/e2e/stats_test.go @@ -19,19 +19,21 @@ var _ = Describe("Podman stats", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman stats with bogus container", func() { diff --git a/test/e2e/stop_test.go b/test/e2e/stop_test.go index 8fffedbb9..97c9287b9 100644 --- a/test/e2e/stop_test.go +++ b/test/e2e/stop_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman stop", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman stop bogus container", func() { @@ -59,6 +59,15 @@ var _ = Describe("Podman stop", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman stop container by name", func() { + session := podmanTest.RunTopContainer("test1") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"container", "stop", "test1"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman stop stopped container", func() { session := podmanTest.RunTopContainer("test1") session.WaitWithDefaultTimeout() @@ -73,7 +82,7 @@ var _ = Describe("Podman stop", func() { Expect(session3.ExitCode()).To(Equal(0)) }) - It("podman stop all containers", func() { + It("podman stop all containers -t", func() { session := podmanTest.RunTopContainer("test1") session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) @@ -98,6 +107,32 @@ var _ = Describe("Podman stop", func() { Expect(output).To(ContainSubstring(cid3)) }) + It("podman stop container --time", func() { + session := podmanTest.RunTopContainer("test4") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid1 := session.OutputToString() + + session = podmanTest.Podman([]string{"stop", "--time", "1", "test4"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output := session.OutputToString() + Expect(output).To(ContainSubstring(cid1)) + }) + + It("podman stop container --timeout", func() { + session := podmanTest.RunTopContainer("test5") + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + cid1 := session.OutputToString() + + session = podmanTest.Podman([]string{"stop", "--timeout", "1", "test5"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + output := session.OutputToString() + Expect(output).To(ContainSubstring(cid1)) + }) + It("podman stop latest containers", func() { session := podmanTest.RunTopContainer("test1") session.WaitWithDefaultTimeout() diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go new file mode 100644 index 000000000..92787f17c --- /dev/null +++ b/test/e2e/system_df_test.go @@ -0,0 +1,62 @@ +// +build !remoteclient + +package integration + +import ( + "fmt" + "os" + "strings" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("podman system df", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman system df", func() { + session := podmanTest.Podman([]string{"create", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"volume", "create", "data"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"create", "-v", "data:/data", "--name", "container1", "busybox"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"system", "df"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(len(session.OutputToStringArray())).To(Equal(4)) + images := strings.Fields(session.OutputToStringArray()[1]) + containers := strings.Fields(session.OutputToStringArray()[2]) + volumes := strings.Fields(session.OutputToStringArray()[3]) + Expect(images[1]).To(Equal("2")) + Expect(containers[1]).To(Equal("2")) + Expect(volumes[2]).To(Equal("1")) + }) +}) diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go index 252361288..52efc9fca 100644 --- a/test/e2e/systemd_test.go +++ b/test/e2e/systemd_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "io/ioutil" "os" @@ -21,11 +20,13 @@ var _ = Describe("Podman systemd", func() { ) BeforeEach(func() { + SkipIfRootless() tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() systemd_unit_file = `[Unit] Description=redis container @@ -42,8 +43,8 @@ WantedBy=multi-user.target AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman start container by systemd", func() { diff --git a/test/e2e/tag_test.go b/test/e2e/tag_test.go index 9f67eaf80..ff0ac31c4 100644 --- a/test/e2e/tag_test.go +++ b/test/e2e/tag_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman tag", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman tag shortname:latest", func() { diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go index 067358468..2d3a5629c 100644 --- a/test/e2e/top_test.go +++ b/test/e2e/top_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman top", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman top without container name or id", func() { @@ -65,6 +65,17 @@ var _ = Describe("Podman top", func() { Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) }) + It("podman container top on container", func() { + session := podmanTest.Podman([]string{"container", "run", "-d", ALPINE, "top", "-d", "2"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"container", "top", "-l"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1)) + }) + It("podman top with options", func() { session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"}) session.WaitWithDefaultTimeout() diff --git a/test/e2e/tree_test.go b/test/e2e/tree_test.go new file mode 100644 index 000000000..9740adada --- /dev/null +++ b/test/e2e/tree_test.go @@ -0,0 +1,64 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image tree", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + }) + + It("podman image tree", func() { + if podmanTest.RemoteTest { + Skip("Does not work on remote client") + } + session := podmanTest.Podman([]string{"pull", "docker.io/library/busybox:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + dockerfile := `FROM docker.io/library/busybox:latest +RUN mkdir hello +RUN touch test.txt +ENV foo=bar +` + podmanTest.BuildImage(dockerfile, "test:latest", "true") + + session = podmanTest.Podman([]string{"image", "tree", "test:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"image", "tree", "--whatrequires", "docker.io/library/busybox:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"rmi", "test:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"rmi", "docker.io/library/busybox:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) +}) diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go index 0d36266f6..493c4a7d5 100644 --- a/test/e2e/trust_test.go +++ b/test/e2e/trust_test.go @@ -4,7 +4,6 @@ package integration import ( "encoding/json" - "fmt" "io/ioutil" "os" "path/filepath" @@ -27,14 +26,15 @@ var _ = Describe("Podman trust", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman image trust show", func() { diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go index 68a462bdb..f546158a9 100644 --- a/test/e2e/version_test.go +++ b/test/e2e/version_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -27,8 +26,8 @@ var _ = Describe("Podman version", func() { AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman version", func() { @@ -37,4 +36,24 @@ var _ = Describe("Podman version", func() { Expect(session.ExitCode()).To(Equal(0)) Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2)) }) + + It("podman version --format json", func() { + session := podmanTest.Podman([]string{"version", "--format", "json"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.IsJSONOutputValid()).To(BeTrue()) + }) + + It("podman version --format json", func() { + session := podmanTest.Podman([]string{"version", "--format", "{{ json .}}"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.IsJSONOutputValid()).To(BeTrue()) + }) + + It("podman version --format GO template", func() { + session := podmanTest.Podman([]string{"version", "--format", "{{ .Version }}"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) }) diff --git a/test/e2e/volume_create_test.go b/test/e2e/volume_create_test.go index 50ee63f2a..dccecd457 100644 --- a/test/e2e/volume_create_test.go +++ b/test/e2e/volume_create_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,15 @@ var _ = Describe("Podman volume create", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupVolume() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman create volume", func() { diff --git a/test/e2e/volume_inspect_test.go b/test/e2e/volume_inspect_test.go index d0d5a601e..e7f20ce7b 100644 --- a/test/e2e/volume_inspect_test.go +++ b/test/e2e/volume_inspect_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,15 @@ var _ = Describe("Podman volume inspect", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupVolume() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman inspect volume", func() { diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go index 119d29d9b..1f0177def 100644 --- a/test/e2e/volume_ls_test.go +++ b/test/e2e/volume_ls_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,15 @@ var _ = Describe("Podman volume ls", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupVolume() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman ls volume", func() { diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go index 802f3fc4a..55a95c8c9 100644 --- a/test/e2e/volume_prune_test.go +++ b/test/e2e/volume_prune_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman volume prune", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupVolume() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman prune volume", func() { diff --git a/test/e2e/volume_rm_test.go b/test/e2e/volume_rm_test.go index 6a1e7d0e8..888474670 100644 --- a/test/e2e/volume_rm_test.go +++ b/test/e2e/volume_rm_test.go @@ -1,7 +1,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -22,14 +21,15 @@ var _ = Describe("Podman volume rm", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.CleanupVolume() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman rm volume", func() { diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go index 08da97aa0..5bf0331e5 100644 --- a/test/e2e/wait_test.go +++ b/test/e2e/wait_test.go @@ -3,7 +3,6 @@ package integration import ( - "fmt" "os" . "github.com/containers/libpod/test/utils" @@ -24,14 +23,15 @@ var _ = Describe("Podman wait", func() { os.Exit(1) } podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() podmanTest.RestoreAllArtifacts() }) AfterEach(func() { podmanTest.Cleanup() f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) + processTestResult(f) + }) It("podman wait on bogus container", func() { @@ -66,4 +66,11 @@ var _ = Describe("Podman wait", func() { session = podmanTest.Podman([]string{"wait", "-l"}) session.Wait(20) }) + It("podman container wait on latest container", func() { + session := podmanTest.Podman([]string{"container", "run", "-d", ALPINE, "sleep", "1"}) + session.Wait(20) + Expect(session.ExitCode()).To(Equal(0)) + session = podmanTest.Podman([]string{"container", "wait", "-l"}) + session.Wait(20) + }) }) diff --git a/test/system/000-TEMPLATE b/test/system/000-TEMPLATE new file mode 100644 index 000000000..85e25e921 --- /dev/null +++ b/test/system/000-TEMPLATE @@ -0,0 +1,114 @@ +#!/usr/bin/env bats -*- bats -*- +# +# FIXME: short description of the purpose of this module +# +# FIXME: copy this file to 'NNN-yourtestname.bats' and edit as needed. +# + +load helpers + +@test "podman subcmd - description of this particular test" { + args="some sort of argument list" + run_podman subcmd $args + is "$output" "what we expect" "output from 'podman subcmd $args'" +} + +# vim: filetype=sh + +############################################################################### +# +# FIXME FIXME FIXME: Most of the time you can cut from here on down. +# FIXME FIXME FIXME: The above template is probably enough for many tests. +# FIXME FIXME FIXME: +# FIXME FIXME FIXME: If you need anything more complicated, read on. +# +# FIXME: This is a bloated test template. It provides mostly stuff for you +# FIXME: to remove, plus stuff for you to base your tests on. +# FIXME: +# FIXME: copy this file to 'NNN-yourtestname.bats' and edit as needed. +# FIXME: Read all FIXMEs, act on them as needed, then remove them. +# FIXME: test w/ $ PODMAN=./bin/podman bats test/system/NNN-yourtestname.bats +# + +load helpers + +# FIXME: DELETE THESE LINES UNLESS YOU ABSOLUTELY NEED THEM. +# FIXME: Most tests will not need a custom setup/teardown: they are +# FIXME: provided by helpers.bash. +# FIXME: But if you have to do anything special, these give you the +# FIXME: names of the standard setup/teardown so you can call them +# FIXME: before or after your own additions. +function setup() { + basic_setup + # FIXME: you almost certainly want to do your own setup _after_ basic. +} +function teardown() { + # FIXME: you almost certainly want to do your own teardown _before_ basic. + basic_teardown +} + + +# FIXME: very basic one-pass example +@test "podman FOO - description of test" { + # FIXME: please try to remove this line; that is, try to write tests + # that will pass as both root and rootless. + skip_if_rootless "Short explanation of why this doesn't work rootless" + + # FIXME: template for run commands. Always use 'run_podman'! + # FIXME: The '?' means 'ignore exit status'; use a number if you + # FIXME: expect a precise nonzero code, or omit for 0 (usual case). + # FIXME: NEVER EVER RUN 'podman' DIRECTLY. See helpers.bash for why. + run_podman '?' run -d $IMAGE sh -c 'prep..; echo READY' + cid="$output" + wait_for_ready $cid + + run_podman logs $cid + # FIXME: example of dprint. This will trigger if PODMAN_TEST_DEBUG=FOO + # FIXME: ...or anything that matches the name assigned in the @test line. + dprint "podman logs $cid -> '$output'" + is "$output" "what are we expecting?" "description of this check" + + # Clean up + run_podman rm $cid +} + + +# FIXME: another example, this time with a test table loop +@test "podman FOO - json - template for playing with json output" { + # FIXME: Define a multiline string in tabular form, using '|' as separator. + # FIXME: Each row defines one test. Each column (there may be as many as + # FIXME: you want) is one field. In the case below we have two, a + # FIXME: json field descriptor and an expected value. + tests=" +id | [0-9a-f]\\\{64\\\} +created | [0-9-]\\\+T[0-9:]\\\+\\\.[0-9]\\\+Z +size | -\\\?[0-9]\\\+ +" + + # FIXME: Run a basic podman command. We'll check $output multiple times + # FIXME: in the while loop below. + run_podman history --format json $IMAGE + + # FIXME: parse_table is what does all the work, giving us test cases. + parse_table "$tests" | while read field expect; do + # FIXME: this shows a drawback of BATS and bash: we can't include '|' + # FIXME: in the table, but we need to because some images don't + # FIXME: have a CID. So, yeah, this is ugly -- but rare. + if [ "$field" = "id" ]; then expect="$expect\|<missing>";fi + + # output is an array of dicts; check each one + count=$(echo "$output" | jq '. | length') + i=0 + while [ $i -lt $count ]; do + actual=$(echo "$output" | jq -r ".[$i].$field") + # FIXME: please be sure to note the third field! + # FIXME: that's the test name. Make it something useful! Include + # FIXME: loop variables whenever possible. Don't just say "my test" + is "$actual" "$expect\$" "jq .[$i].$field" + i=$(expr $i + 1) + done + done +} + + +# vim: filetype=sh diff --git a/test/system/001-basic.bats b/test/system/001-basic.bats new file mode 100644 index 000000000..85b9bc1ca --- /dev/null +++ b/test/system/001-basic.bats @@ -0,0 +1,50 @@ +#!/usr/bin/env bats +# +# Simplest set of podman tests. If any of these fail, we have serious problems. +# + +load helpers + +# Override standard setup! We don't yet trust podman-images or podman-rm +function setup() { + : +} + +@test "podman version emits reasonable output" { + run_podman version + + is "${lines[0]}" "Version:[ ]\+[1-9][0-9.]\+" "Version line 1" + is "$output" ".*Go Version: \+" "'Go Version' in output" + is "$output" ".*RemoteAPI Version: \+" "API version in output" +} + + +@test "podman can pull an image" { + run_podman pull $IMAGE +} + +# This is for development only; it's intended to make sure our timeout +# in run_podman continues to work. This test should never run in production +# because it will, by definition, fail. +@test "timeout" { + if [ -z "$PODMAN_RUN_TIMEOUT_TEST" ]; then + skip "define \$PODMAN_RUN_TIMEOUT_TEST to enable this test" + fi + PODMAN_TIMEOUT=10 run_podman run $IMAGE sleep 90 + echo "*** SHOULD NEVER GET HERE" +} + + +# Too many tests rely on jq for parsing JSON. +# +# If absolutely necessary, one could establish a convention such as +# defining PODMAN_TEST_SKIP_JQ=1 and adding a skip_if_no_jq() helper. +# For now, let's assume this is not absolutely necessary. +@test "jq is installed and produces reasonable output" { + type -path jq >/dev/null || die "FATAL: 'jq' tool not found." + + run jq -r .a.b < <(echo '{ "a": { "b" : "you found me" } }') + is "$output" "you found me" "sample invocation of 'jq'" +} + +# vim: filetype=sh diff --git a/test/system/005-info.bats b/test/system/005-info.bats new file mode 100644 index 000000000..7dcc78838 --- /dev/null +++ b/test/system/005-info.bats @@ -0,0 +1,54 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman info - basic test" { + run_podman info + + expected_keys=" +BuildahVersion: *[0-9.]\\\+ +Conmon:\\\s\\\+package: +Distribution: +OCIRuntime:\\\s\\\+package: +os: +rootless: +insecure registries: +store: +GraphDriverName: +GraphRoot: +GraphStatus: +ImageStore:\\\s\\\+number: 1 +RunRoot: +" + while read expect; do + is "$output" ".*$expect" "output includes '$expect'" + done < <(parse_table "$expected_keys") +} + +@test "podman info - json" { + run_podman info --format=json + + expr_nvr="[a-z0-9-]\\\+-[a-z0-9.]\\\+-[a-z0-9]\\\+\." + expr_path="/[a-z0-9\\\/.]\\\+\\\$" + + tests=" +host.BuildahVersion | [0-9.] +host.Conmon.package | $expr_nvr +host.Conmon.path | $expr_path +host.OCIRuntime.package | $expr_nvr +host.OCIRuntime.path | $expr_path +store.ConfigFile | $expr_path +store.GraphDriverName | [a-z0-9]\\\+\\\$ +store.GraphRoot | $expr_path +store.ImageStore.number | 1 +" + + parse_table "$tests" | while read field expect; do + actual=$(echo "$output" | jq -r ".$field") + dprint "# actual=<$actual> expect=<$expect>" + is "$actual" "$expect" "jq .$field" + done + +} + +# vim: filetype=sh diff --git a/test/system/010-images.bats b/test/system/010-images.bats new file mode 100644 index 000000000..1c9577e34 --- /dev/null +++ b/test/system/010-images.bats @@ -0,0 +1,46 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman images - basic output" { + run_podman images -a + + is "${lines[0]}" "REPOSITORY *TAG *IMAGE ID *CREATED *SIZE" "header line" + is "${lines[1]}" "$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME *$PODMAN_TEST_IMAGE_TAG *[0-9a-f]\+" "podman images output" +} + +@test "podman images - custom formats" { + tests=" +--format {{.ID}} | [0-9a-f]\\\{12\\\} +--format {{.ID}} --no-trunc | sha256:[0-9a-f]\\\{64\\\} +--format {{.Repository}}:{{.Tag}} | $PODMAN_TEST_IMAGE_FQN +" + + parse_table "$tests" | while read fmt expect; do + run_podman images $fmt + is "$output" "$expect\$" "podman images $fmt" + done + +} + + +@test "podman images - json" { + tests=" +names[0] | $PODMAN_TEST_IMAGE_FQN +id | [0-9a-f]\\\{64\\\} +digest | sha256:[0-9a-f]\\\{64\\\} +created | [0-9-]\\\+T[0-9:]\\\+\\\.[0-9]\\\+Z +size | [0-9]\\\+ +" + + run_podman images -a --format json + + parse_table "$tests" | while read field expect; do + actual=$(echo "$output" | jq -r ".[0].$field") + dprint "# actual=<$actual> expect=<$expect}>" + is "$actual" "$expect" "jq .$field" + done + +} + +# vim: filetype=sh diff --git a/test/system/015-help.bats b/test/system/015-help.bats new file mode 100644 index 000000000..8e07b8822 --- /dev/null +++ b/test/system/015-help.bats @@ -0,0 +1,95 @@ +#!/usr/bin/env bats +# +# Tests based on 'podman help' +# +# Find all commands listed by 'podman --help'. Run each one, make sure it +# provides its own --help output. If the usage message ends in '[command]', +# treat it as a subcommand, and recurse into its own list of sub-subcommands. +# +# Any usage message that ends in '[flags]' is interpreted as a command +# that takes no further arguments; we confirm by running with 'invalid-arg' +# and confirming that it exits with error status and message. +# +load helpers + +# run 'podman help', parse the output looking for 'Available Commands'; +# return that list. +function podman_commands() { + dprint "$@" + run_podman help "$@" |\ + awk '/^Available Commands:/{ok=1;next}/^Flags:/{ok=0}ok { print $1 }' |\ + grep . + "$output" +} + + +function check_help() { + local count=0 + local subcommands_found=0 + + for cmd in $(podman_commands "$@"); do + dprint "podman $@ $cmd --help" + run_podman "$@" $cmd --help + + # The line immediately after 'Usage:' gives us a 1-line synopsis + usage=$(echo "$output" | grep -A1 '^Usage:' | tail -1) + [ -n "$usage" ] || die "podman $cmd: no Usage message found" + + # If usage ends in '[command]', recurse into subcommands + if expr "$usage" : '.*\[command\]$' >/dev/null; then + subcommands_found=$(expr $subcommands_found + 1) + check_help "$@" $cmd + continue + fi + + # If usage ends in '[flag]', command takes no more arguments. + # Confirm that by running with 'invalid-arg' and expecting failure. + if expr "$usage" : '.*\[flags\]$' >/dev/null; then + if [ "$cmd" != "help" ]; then + dprint "podman $@ $cmd invalid-arg" + run_podman 125 "$@" $cmd invalid-arg + is "$output" "Error: .* takes no arguments" \ + "'podman $@ $cmd' with extra (invalid) arguments" + fi + fi + + # If usage has required arguments, try running without them + if expr "$usage" : '.*\[flags\] [A-Z]' >/dev/null; then + dprint "podman $@ $cmd (without required args)" + run_podman 125 "$@" $cmd + is "$output" "Error:" + fi + + count=$(expr $count + 1) + done + + # Any command that takes subcommands, must throw error if called + # without one. + dprint "podman $@" + run_podman 125 "$@" + is "$output" "Error: missing command .*$@ COMMAND" + + # Assume that 'NoSuchCommand' is not a command + dprint "podman $@ NoSuchCommand" + run_podman 125 "$@" NoSuchCommand + is "$output" "Error: unrecognized command .*$@ NoSuchCommand" + + # This can happen if the output of --help changes, such as between + # the old command parser and cobra. + [ $count -gt 0 ] || \ + die "Internal error: no commands found in 'podman help $@' list" + + # At least the top level must have some subcommands + if [ -z "$*" -a $subcommands_found -eq 0 ]; then + die "Internal error: did not find any podman subcommands" + fi +} + + +@test "podman help - basic tests" { + # Called with no args -- start with 'podman --help'. check_help() will + # recurse for any subcommands. + check_help +} + +# vim: filetype=sh diff --git a/test/system/030-run.bats b/test/system/030-run.bats new file mode 100644 index 000000000..8ae68f33d --- /dev/null +++ b/test/system/030-run.bats @@ -0,0 +1,34 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman run - basic tests" { + rand=$(random_string 30) + tests=" +true | 0 | +false | 1 | +sh -c 'exit 32' | 32 | +echo $rand | 0 | $rand +/no/such/command | 127 | Error: container create failed:.*exec:.* no such file or dir +/etc | 126 | Error: container create failed:.*exec:.* permission denied +" + + while read cmd expected_rc expected_output; do + if [ "$expected_output" = "''" ]; then expected_output=""; fi + + # THIS IS TRICKY: this is what lets us handle a quoted command. + # Without this incantation (and the "$@" below), the cmd string + # gets passed on as individual tokens: eg "sh" "-c" "'exit" "32'" + # (note unmatched opening and closing single-quotes in the last 2). + # That results in a bizarre and hard-to-understand failure + # in the BATS 'run' invocation. + # This should really be done inside parse_table; I can't find + # a way to do so. + eval set "$cmd" + + run_podman $expected_rc run $IMAGE "$@" + is "$output" "$expected_output" "podman run $cmd - output" + done < <(parse_table "$tests") +} + +# vim: filetype=sh diff --git a/test/system/035-logs.bats b/test/system/035-logs.bats new file mode 100644 index 000000000..055865c8d --- /dev/null +++ b/test/system/035-logs.bats @@ -0,0 +1,51 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Basic tests for podman logs +# + +load helpers + +@test "podman logs - basic test" { + rand_string=$(random_string 40) + + run_podman create $IMAGE echo $rand_string + cid="$output" + + run_podman logs $cid + is "$output" "" "logs on created container: empty" + + run_podman start --attach --interactive $cid + is "$output" "$rand_string" "output from podman-start on created ctr" + is "$output" "$rand_string" "logs of started container" + + run_podman logs $cid + is "$output" "$rand_string" "output from podman-logs after container is run" + + run_podman rm $cid +} + +@test "podman logs - multi" { + # Simple helper to make the container starts, below, easier to read + local -a cid + doit() { + run_podman run --rm -d --name "$1" $IMAGE sh -c "$2"; + cid+=($(echo "${output:0:12}")) + } + + # Not really a guarantee that we'll get a-b-c-d in order, but it's + # the best we can do. The trailing 'sleep' in each container + # minimizes the chance of a race condition in which the container + # is removed before 'podman logs' has a chance to wake up and read + # the final output. + doit c1 "echo a;sleep 10;echo d;sleep 3" + doit c2 "sleep 1;echo b;sleep 2;echo c;sleep 3" + + run_podman logs -f c1 c2 + is "$output" \ + "${cid[0]} a +${cid[1]} b +${cid[1]} c +${cid[0]} d" "Sequential output from logs" +} + +# vim: filetype=sh diff --git a/test/system/040-ps.bats b/test/system/040-ps.bats new file mode 100644 index 000000000..dec2df4d5 --- /dev/null +++ b/test/system/040-ps.bats @@ -0,0 +1,38 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman ps - basic tests" { + rand_name=$(random_string 30) + + run_podman run -d --name $rand_name $IMAGE sleep 5 + cid=$output + is "$cid" "[0-9a-f]\{64\}$" + + # Special case: formatted ps + run_podman ps --no-trunc \ + --format '{{.ID}} {{.Image}} {{.Command}} {{.Names}}' + is "$output" "$cid $IMAGE sleep 5 $rand_name" "podman ps" + + + # Plain old regular ps + run_podman ps + is "${lines[1]}" \ + "${cid:0:12} \+$IMAGE \+sleep [0-9]\+ .*second.* $cname"\ + "output from podman ps" + + # OK. Wait for sleep to finish... + run_podman wait $cid + + # ...then make sure container shows up as stopped + run_podman ps -a + is "${lines[1]}" \ + "${cid:0:12} \+$IMAGE *sleep .* Exited .* $rand_name" \ + "podman ps -a" + + + + run_podman rm $cid +} + +# vim: filetype=sh diff --git a/test/system/050-stop.bats b/test/system/050-stop.bats new file mode 100644 index 000000000..093606ece --- /dev/null +++ b/test/system/050-stop.bats @@ -0,0 +1,67 @@ +#!/usr/bin/env bats + +load helpers + +# Very simple test +@test "podman stop - basic test" { + run_podman run -d $IMAGE sleep 60 + cid="$output" + + # Run 'stop'. Time how long it takes. + t0=$SECONDS + run_podman stop $cid + t1=$SECONDS + + # Confirm that container is stopped + run_podman inspect --format '{{.State.Status}} {{.State.ExitCode}}' $cid + is "$output" "exited \+137" "Status and exit code of stopped container" + + # The initial SIGTERM is ignored, so this operation should take + # exactly 10 seconds. Give it some leeway. + delta_t=$(( $t1 - $t0 )) + [ $delta_t -gt 8 ] ||\ + die "podman stop: ran too quickly! ($delta_t seconds; expected >= 10)" + [ $delta_t -le 14 ] ||\ + die "podman stop: took too long ($delta_t seconds; expected ~10)" + + run_podman rm $cid +} + + +# Test fallback + + +# Regression test for #2472 +@test "podman stop - can trap signal" { + # Because the --time and --timeout options can be wonky, try three + # different variations of this test. + for t_opt in '' '--time=5' '--timeout=5'; do + # Run a simple container that logs output on SIGTERM + run_podman run -d $IMAGE sh -c \ + "trap 'echo Received SIGTERM, finishing; exit' SIGTERM; echo READY; while :; do sleep 1; done" + cid="$output" + wait_for_ready $cid + + # Run 'stop' against it... + t0=$SECONDS + run_podman stop $t_opt $cid + t1=$SECONDS + + # ...the container should trap the signal, log it, and exit. + run_podman logs $cid + is "$output" ".*READY.*Received SIGTERM, finishing" "podman stop $t_opt" + + # Exit code should be 0, because container did its own exit + run_podman inspect --format '{{.State.ExitCode}}' $cid + is "$output" "0" "Exit code of stopped container" + + # The 'stop' command should return almost instantaneously + delta_t=$(( $t1 - $t0 )) + [ $delta_t -le 2 ] ||\ + die "podman stop: took too long ($delta_t seconds; expected <= 2)" + + run_podman rm $cid + done +} + +# vim: filetype=sh diff --git a/test/system/060-mount.bats b/test/system/060-mount.bats new file mode 100644 index 000000000..e249b2883 --- /dev/null +++ b/test/system/060-mount.bats @@ -0,0 +1,37 @@ +#!/usr/bin/env bats + +load helpers + + +@test "podman mount - basic test" { + # Only works with root (FIXME: does it work with rootless + vfs?) + skip_if_rootless "mount does not work rootless" + + f_path=/tmp/tmpfile_$(random_string 8) + f_content=$(random_string 30) + + c_name=mount_test_$(random_string 5) + run_podman run --name $c_name $IMAGE \ + sh -c "echo $f_content > $f_path" + + run_podman mount $c_name + mount_path=$output + + test -d $mount_path + test -e "$mount_path/$f_path" + is $(< "$mount_path/$f_path") "$f_content" "contents of file, as read via fs" + + # Make sure that 'podman mount' (no args) returns the expected path + run_podman mount --notruncate + # FIXME: is it worth the effort to validate the CID ($1) ? + reported_mountpoint=$(echo "$output" | awk '{print $2}') + is $reported_mountpoint $mount_path "mountpoint reported by 'podman mount'" + + # umount, and make sure files are gone + run_podman umount $c_name + if [ -e "$mount_path/$f_path" ]; then + die "Mounted file exists even after umount: $mount_path/$f_path" + fi +} + +# vim: filetype=sh diff --git a/test/system/070-build.bats b/test/system/070-build.bats new file mode 100644 index 000000000..25eb36c58 --- /dev/null +++ b/test/system/070-build.bats @@ -0,0 +1,28 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Tests for podman build +# + +load helpers + +@test "podman build - basic test" { + rand_filename=$(random_string 20) + rand_content=$(random_string 50) + + tmpdir=$PODMAN_TMPDIR/build-test + run mkdir -p $tmpdir || die "Could not mkdir $tmpdir" + dockerfile=$tmpdir/Dockerfile + cat >$dockerfile <<EOF +FROM $IMAGE +RUN echo $rand_content > /$rand_filename +EOF + + run_podman build -t build_test --format=docker $tmpdir + + run_podman run --rm build_test cat /$rand_filename + is "$output" "$rand_content" "reading generated file in image" + + run_podman rmi build_test +} + +# vim: filetype=sh diff --git a/test/system/075-exec.bats b/test/system/075-exec.bats new file mode 100644 index 000000000..a12d28b32 --- /dev/null +++ b/test/system/075-exec.bats @@ -0,0 +1,30 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Tests for podman exec +# + +load helpers + +@test "podman exec - basic test" { + rand_filename=$(random_string 20) + rand_content=$(random_string 50) + + # Start a container. Write random content to random file, then stay + # alive as long as file exists. (This test will remove that file soon.) + run_podman run -d $IMAGE sh -c \ + "echo $rand_content >/$rand_filename;echo READY;while [ -f /$rand_filename ]; do sleep 1; done" + cid="$output" + wait_for_ready $cid + + run_podman exec $cid sh -c "cat /$rand_filename" + is "$output" "$rand_content" "Can exec and see file in running container" + + run_podman exec $cid rm -f /$rand_filename + + run_podman wait $cid + is "$output" "0" "output from podman wait (container exit code)" + + run_podman rm $cid +} + +# vim: filetype=sh diff --git a/test/system/110-history.bats b/test/system/110-history.bats new file mode 100644 index 000000000..84a1e42b4 --- /dev/null +++ b/test/system/110-history.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman history - basic tests" { + tests=" + | .*[0-9a-f]\\\{12\\\} .* CMD .* LABEL +--format '{{.ID}} {{.Created}}' | .*[0-9a-f]\\\{12\\\} .* ago +--human=false | .*[0-9a-f]\\\{12\\\} *[0-9-]\\\+T[0-9:]\\\+Z +-qH | .*[0-9a-f]\\\{12\\\} +--no-trunc | .*[0-9a-f]\\\{64\\\} +" + + parse_table "$tests" | while read options expect; do + if [ "$options" = "''" ]; then options=; fi + + eval set -- "$options" + + run_podman history "$@" $IMAGE + is "$output" "$expect" "podman history $options" + done +} + +@test "podman history - json" { + tests=" +id | [0-9a-f]\\\{64\\\} +created | [0-9-]\\\+T[0-9:]\\\+\\\.[0-9]\\\+Z +size | -\\\?[0-9]\\\+ +" + + run_podman history --format json $IMAGE + + parse_table "$tests" | while read field expect; do + # HACK: we can't include '|' in the table + if [ "$field" = "id" ]; then expect="$expect\|<missing>";fi + + # output is an array of dicts; check each one + count=$(echo "$output" | jq '. | length') + i=0 + while [ $i -lt $count ]; do + actual=$(echo "$output" | jq -r ".[$i].$field") + is "$actual" "$expect\$" "jq .[$i].$field" + i=$(expr $i + 1) + done + done + +} + +# vim: filetype=sh diff --git a/test/system/120-load.bats b/test/system/120-load.bats new file mode 100644 index 000000000..dedfe6172 --- /dev/null +++ b/test/system/120-load.bats @@ -0,0 +1,96 @@ +#!/usr/bin/env bats -*- bats -*- +# +# tests for podman load +# + +load helpers + +# Custom helpers for this test only. These just save us having to duplicate +# the same thing four times (two tests, each with -i and stdin). +# +# initialize, read image ID and name +get_iid_and_name() { + run_podman images --format '{{.ID}} {{.Repository}}:{{.Tag}}' + read iid img_name < <(echo "$output") + + archive=$PODMAN_TMPDIR/myimage-$(random_string 8).tar +} + +# Simple verification of image ID and name +verify_iid_and_name() { + run_podman images --format '{{.ID}} {{.Repository}}:{{.Tag}}' + read new_iid new_img_name < <(echo "$output") + + # Verify + is "$new_iid" "$iid" "Image ID of loaded image == original" + is "$new_img_name" "$1" "Name & tag of restored image" +} + + +@test "podman load - by image ID" { + # FIXME: how to build a simple archive instead? + get_iid_and_name + + # Save image by ID, and remove it. + run_podman save $iid -o $archive + run_podman rmi $iid + + # Load using -i; IID should be preserved, but name is not. + run_podman load -i $archive + verify_iid_and_name "<none>:<none>" + + # Same as above, using stdin + run_podman rmi $iid + run_podman load < $archive + verify_iid_and_name "<none>:<none>" + + # Cleanup: since load-by-iid doesn't preserve name, re-tag it; + # otherwise our global teardown will rmi and re-pull our standard image. + run_podman tag $iid $img_name +} + +@test "podman load - by image name" { + get_iid_and_name + run_podman save $img_name -o $archive + run_podman rmi $iid + + # Load using -i; this time the image should be tagged. + run_podman load -i $archive + verify_iid_and_name $img_name + + # Same as above, using stdin + run_podman rmi $iid + run_podman load < $archive + verify_iid_and_name $img_name +} + +@test "podman load - NAME and NAME:TAG arguments work (requires: #2674)" { + get_iid_and_name + run_podman save $iid -o $archive + run_podman rmi $iid + + # Load with just a name (note: names must be lower-case) + random_name=$(random_string 20 | tr A-Z a-z) + run_podman load -i $archive $random_name + verify_iid_and_name "localhost/$random_name:latest" + + # Load with NAME:TAG arg + run_podman rmi $iid + random_tag=$(random_string 10 | tr A-Z a-z) + run_podman load -i $archive $random_name:$random_tag + verify_iid_and_name "localhost/$random_name:$random_tag" + + # Cleanup: restore desired image name + run_podman tag $iid $img_name + run_podman rmi "$random_name:$random_tag" +} + + +@test "podman load - will not read from tty" { + run_podman 125 load + is "$output" \ + "Error: cannot read from terminal. Use command-line redirection" \ + "Diagnostic from 'podman load' without redirection or -i" +} + +# vim: filetype=sh diff --git a/test/system/200-pod-top.bats b/test/system/200-pod-top.bats new file mode 100644 index 000000000..0200df00d --- /dev/null +++ b/test/system/200-pod-top.bats @@ -0,0 +1,41 @@ +#!/usr/bin/env bats + +load helpers + +@test "podman pod top - containers in different PID namespaces" { + # With infra=false, we don't get a /pause container (we also + # don't pull k8s.gcr.io/pause ) + no_infra='--infra=false' + run_podman pod create $no_infra + podid="$output" + + # Start two containers... + run_podman run -d --pod $podid $IMAGE top -d 2 + cid1="$output" + run_podman run -d --pod $podid $IMAGE top -d 2 + cid2="$output" + + # ...and wait for them to actually start. + wait_for_output "PID \+PPID \+USER " $cid1 + wait_for_output "PID \+PPID \+USER " $cid2 + + # Both containers have emitted at least one top-like line. + # Now run 'pod top', and expect two 'top -d 2' processes running. + run_podman pod top $podid + is "$output" ".*root.*top -d 2.*root.*top -d 2" "two 'top' containers" + + # By default (podman pod create w/ default --infra) there should be + # a /pause container. + # FIXME: sometimes there is, sometimes there isn't. If anyone ever + # actually figures this out, please either reenable this line or + # remove it entirely. + if [ -z "$no_infra" ]; then + is "$output" ".*0 \+1 \+0 \+[0-9. ?s]\+/pause" "there is a /pause container" + fi + + # Clean up + run_podman pod rm -f $podid +} + + +# vim: filetype=sh diff --git a/test/system/300-cli-parsing.bats b/test/system/300-cli-parsing.bats new file mode 100644 index 000000000..92c073102 --- /dev/null +++ b/test/system/300-cli-parsing.bats @@ -0,0 +1,15 @@ +#!/usr/bin/env bats -*- bats -*- +# +# Various command-line parsing regression tests that don't fit in elsewhere +# + +load helpers + +@test "podman cli parsing - quoted args - #2574" { + # 1.1.2 fails with: + # Error: invalid argument "true=\"false\"" for "-l, --label" \ + # flag: parse error on line 1, column 5: bare " in non-quoted-field + run_podman run --rm --label 'true="false"' $IMAGE true +} + +# vim: filetype=sh diff --git a/test/system/README.md b/test/system/README.md new file mode 100644 index 000000000..6ac408f4e --- /dev/null +++ b/test/system/README.md @@ -0,0 +1,82 @@ +Quick overview of podman system tests. The idea is to use BATS, +but with a framework for making it easy to add new tests and to +debug failures. + +Quick Start +=========== + +Look at [030-run.bats](030-run.bats) for a simple but packed example. +This introduces the basic set of helper functions: + +* `setup` (implicit) - resets container storage so there's +one and only one (standard) image, and no running containers. + +* `parse_table` - you can define tables of inputs and expected results, +then read those in a `while` loop. This makes it easy to add new tests. +Because bash is not a programming language, the caller of `parse_table` +sometimes needs to massage the returned values; `015-run.bats` offers +examples of how to deal with the more typical such issues. + +* `run_podman` - runs command defined in `$PODMAN` (default: 'podman' +but could also be './bin/podman' or 'podman-remote'), with a timeout. +Checks its exit status. + +* `is` - compare actual vs expected output. Emits a useful diagnostic +on failure. + +* `die` - output a properly-formatted message to stderr, and fail test + +* `skip_if_rootless` - if rootless, skip this test with a helpful message. + +* `random_string` - returns a pseudorandom alphanumeric string + +Test files are of the form `NNN-name.bats` where NNN is a three-digit +number. Please preserve this convention, it simplifies viewing the +directory and understanding test order. In particular, `00x` tests +should be reserved for a first-pass fail-fast subset of tests: + + bats test/system/00*.bats || exit 1 + bats test/system + +...the goal being to provide quick feedback on catastrophic failures +without having to wait for the entire test suite. + + +Analyzing test failures +======================= + +The top priority for this scheme is to make it easy to diagnose +what went wrong. To that end, `podman_run` always logs all invoked +commands, their output and exit codes. In a normal run you will never +see this, but BATS will display it on failure. The goal here is to +give you everything you need to diagnose without having to rerun tests. + +The `is` comparison function is designed to emit useful diagnostics, +in particular, the actual and expected strings. Please do not use +the horrible BATS standard of `[ x = y ]`; that's nearly useless +for tracking down failures. + +If the above are not enough to help you track down a failure: + + +Debugging tests +--------------- + +Some functions have `dprint` statements. To see the output of these, +set `PODMAN_TEST_DEBUG="funcname"` where `funcname` is the name of +the function or perhaps just a substring. + + +Requirements +============ + +The `jq` tool is needed for parsing JSON output. + + +Further Details +=============== + +TBD. For now, look in [helpers.bash](helpers.bash); each helper function +has (what are intended to be) helpful header comments. For even more +examples, see and/or run `helpers.t`; that's a regression test +and provides a thorough set of examples of how the helpers work. diff --git a/test/system/TODO.md b/test/system/TODO.md new file mode 100644 index 000000000..f6110d2e9 --- /dev/null +++ b/test/system/TODO.md @@ -0,0 +1,105 @@ +![PODMAN logo](../../logo/podman-logo-source.svg) + +# Overview + +System tests exercise Podman in the context of a complete, composed environment from +distribution packages. It should match as closely as possible to how an end-user +would experience a fresh-install. Dependencies on external configuration and resources +must be kept minimal, and the tests must be generic and vendor-neutral. + +The system-tests must execute cleanly on all tested platforms. They may optionally +be executed during continuous-integration testing of code-changes, after all other +testing completes successfully. For a list of tested platforms, please see [the +CI configuration file.](../../.cirrus.yml) + + +# Execution + +When working from a clone of [the libpod repository](https://github.com/containers/libpod), +the main entry-point for humans and automation is `make localsystem`. When operating +from a packaged version of the system-tests, the entry-point may vary as appropriate. +Running the packaged system-tests assumes the version of Podman matches the test +version, and all standard dependencies are installed. + + +# Test Design and overview + +System-tests should be high-level and user work-flow oriented. For example, consider +how multiple Podman invocations would be used together by an end-user. The set of +related commands should be considered a single test. If one or more intermediate +commands fail, the test could still pass if the end-result is still achieved. + + +# *TODO*: List of needed System-tests + +***Note***: Common operations (like `rm` and `rmi` for cleanup/reset) +have been omitted as they are verified by repeated implied use. + +- [ ] pull, build, run, attach, commit, diff, inspect + + - Pull existing image from registry + - Build new image FROM explicitly pulled image + - Run built container in detached mode + - Attach to running container, execute command to modify storage. + - Commit running container to new image w/ changed ENV VAR + - Verify attach + commit using diff + - verify changed ENV VAR with inspect + +- [ ] Implied pull, create, start, exec, log, stop, wait, rm + + - Create non-existing local image + - start stopped container + - exec simple command in running container + - verify exec result with log + - wait on running container + - stop running container with 2 second timeout + - verify wait in 4 seconds or less + - verify stopped by rm **without** --force + +- [ ] Implied pull, build, export, modify, import, tag, run, kill + + - Build from Dockerfile FROM non-existing local image + - Export built container as tarball + - Modify tarball contents + - Import tarball + - Tag imported image + - Run imported image to confirm tarball modification, block on non-special signal + - Kill can send non-TERM/KILL signal to container to exit + - Confirm exit within timeout + +- [ ] Container runlabel, exists, checkpoint, exists, restore, stop, prune + + - Using pre-existing remote image, start it with 'podman container runlabel --pull' + - Run a named container that exits immediatly + - Confirm 'container exists' zero exit (both containers) + - Checkpoint the running container + - Confirm 'container exists' non-zero exit (runlabel container) + - Confirm 'container exists' zero exit (named container) + - Run 'container restore' + - Confirm 'container exists' zero exit (both containers) + - Stop container + - Run 'container prune' + - Confirm `podman ps -a` lists no containers + + +# TODO: List of commands to be combined into additional workflows above. + +- podman-remote (workflow TBD) +- history +- image +- load +- mount +- pause +- pod +- port +- login, push, & logout (difficult, save for last) +- restart +- save +- search +- stats +- top +- umount, unmount +- unpause +- volume +- `--namespace` +- `--storage-driver` diff --git a/test/system/helpers.bash b/test/system/helpers.bash new file mode 100644 index 000000000..431228498 --- /dev/null +++ b/test/system/helpers.bash @@ -0,0 +1,349 @@ +# -*- bash -*- + +# Podman command to run; may be podman-remote +PODMAN=${PODMAN:-podman} + +# Standard image to use for most tests +PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"} +PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"} +PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"alpine_labels"} +PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"latest"} +PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG" + +# Because who wants to spell that out each time? +IMAGE=$PODMAN_TEST_IMAGE_FQN + +# Default timeout for a podman command. +PODMAN_TIMEOUT=${PODMAN_TIMEOUT:-60} + +############################################################################### +# BEGIN setup/teardown tools + +# Provide common setup and teardown functions, but do not name them such! +# That way individual tests can override with their own setup/teardown, +# while retaining the ability to include these if they so desire. + +# Setup helper: establish a test environment with exactly the images needed +function basic_setup() { + # Clean up all containers + run_podman rm --all --force + + # Clean up all images except those desired + found_needed_image= + run_podman images --all --format '{{.Repository}}:{{.Tag}} {{.ID}}' + for line in "${lines[@]}"; do + set $line + if [ "$1" == "$PODMAN_TEST_IMAGE_FQN" ]; then + found_needed_image=1 + else + echo "# setup(): removing stray images" >&3 + run_podman rmi --force "$1" >/dev/null 2>&1 || true + run_podman rmi --force "$2" >/dev/null 2>&1 || true + fi + done + + # Make sure desired images are present + if [ -z "$found_needed_image" ]; then + run_podman pull "$PODMAN_TEST_IMAGE_FQN" + fi + + # Argh. Although BATS provides $BATS_TMPDIR, it's just /tmp! + # That's bloody worthless. Let's make our own, in which subtests + # can write whatever they like and trust that it'll be deleted + # on cleanup. + # TODO: do this outside of setup, so it carries across tests? + PODMAN_TMPDIR=$(mktemp -d --tmpdir=${BATS_TMPDIR:-/tmp} podman_bats.XXXXXX) +} + +# Basic teardown: remove all pods and containers +function basic_teardown() { + echo "# [teardown]" >&2 + run_podman '?' pod rm --all --force + run_podman '?' rm --all --force + + /bin/rm -rf $PODMAN_TMPDIR +} + + +# Provide the above as default methods. +function setup() { + basic_setup +} + +function teardown() { + basic_teardown +} + + +# Helpers useful for tests running rmi +function archive_image() { + local image=$1 + + # FIXME: refactor? + archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) + archive=$BATS_TMPDIR/$archive_basename.tar + + run_podman save -o $archive $image +} + +function restore_image() { + local image=$1 + + archive_basename=$(echo $1 | tr -c a-zA-Z0-9._- _) + archive=$BATS_TMPDIR/$archive_basename.tar + + run_podman restore $archive +} + +# END setup/teardown tools +############################################################################### +# BEGIN podman helpers + +################ +# run_podman # Invoke $PODMAN, with timeout, using BATS 'run' +################ +# +# This is the preferred mechanism for invoking podman: first, it +# invokes $PODMAN, which may be 'podman-remote' or '/some/path/podman'. +# +# Second, we use 'timeout' to abort (with a diagnostic) if something +# takes too long; this is preferable to a CI hang. +# +# Third, we log the command run and its output. This doesn't normally +# appear in BATS output, but it will if there's an error. +# +# Next, we check exit status. Since the normal desired code is 0, +# that's the default; but the first argument can override: +# +# run_podman 125 nonexistent-subcommand +# run_podman '?' some-other-command # let our caller check status +# +# Since we use the BATS 'run' mechanism, $output and $status will be +# defined for our caller. +# +function run_podman() { + # Number as first argument = expected exit code; default 0 + expected_rc=0 + case "$1" in + [0-9]) expected_rc=$1; shift;; + [1-9][0-9]) expected_rc=$1; shift;; + [12][0-9][0-9]) expected_rc=$1; shift;; + '?') expected_rc= ; shift;; # ignore exit code + esac + + # stdout is only emitted upon error; this echo is to help a debugger + echo "\$ $PODMAN $*" + run timeout --foreground -v --kill=10 $PODMAN_TIMEOUT $PODMAN "$@" + # without "quotes", multiple lines are glommed together into one + if [ -n "$output" ]; then + echo "$output" + fi + if [ "$status" -ne 0 ]; then + echo -n "[ rc=$status "; + if [ -n "$expected_rc" ]; then + if [ "$status" -eq "$expected_rc" ]; then + echo -n "(expected) "; + else + echo -n "(** EXPECTED $expected_rc **) "; + fi + fi + echo "]" + fi + + if [ "$status" -eq 124 ]; then + if expr "$output" : ".*timeout: sending" >/dev/null; then + echo "*** TIMED OUT ***" + false + fi + fi + + if [ -n "$expected_rc" ]; then + if [ "$status" -ne "$expected_rc" ]; then + die "exit code is $status; expected $expected_rc" + fi + fi +} + + +# Wait for certain output from a container, indicating that it's ready. +function wait_for_output { + local sleep_delay=5 + local how_long=$PODMAN_TIMEOUT + local expect= + local cid= + + # Arg processing. A single-digit number is how long to sleep between + # iterations; a 2- or 3-digit number is the total time to wait; all + # else are, in order, the string to expect and the container name/ID. + local i + for i in "$@"; do + if expr "$i" : '[0-9]\+$' >/dev/null; then + if [ $i -le 9 ]; then + sleep_delay=$i + else + how_long=$i + fi + elif [ -z "$expect" ]; then + expect=$i + else + cid=$i + fi + done + + [ -n "$cid" ] || die "FATAL: wait_for_ready: no container name/ID in '$*'" + + t1=$(expr $SECONDS + $how_long) + while [ $SECONDS -lt $t1 ]; do + run_podman logs $cid + if expr "$output" : ".*$expect" >/dev/null; then + return + fi + + sleep $sleep_delay + done + + die "timed out waiting for '$expect' from $cid" +} + +# Shortcut for the lazy +function wait_for_ready { + wait_for_output 'READY' "$@" +} + +# END podman helpers +############################################################################### +# BEGIN miscellaneous tools + +###################### +# skip_if_rootless # ...with an optional message +###################### +function skip_if_rootless() { + if [ "$(id -u)" -eq 0 ]; then + return + fi + + skip "${1:-not applicable under rootless podman}" +} + + +######### +# die # Abort with helpful message +######### +function die() { + echo "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv" >&2 + echo "#| FAIL: $*" >&2 + echo "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" >&2 + false +} + + +######## +# is # Compare actual vs expected string; fail w/diagnostic if mismatch +######## +# +# Compares given string against expectations, using 'expr' to allow patterns. +# +# Examples: +# +# is "$actual" "$expected" "descriptive test name" +# is "apple" "orange" "name of a test that will fail in most universes" +# is "apple" "[a-z]\+" "this time it should pass" +# +function is() { + local actual="$1" + local expect="$2" + local testname="${3:-FIXME}" + + if [ -z "$expect" ]; then + if [ -z "$actual" ]; then + return + fi + expect='[no output]' + elif expr "$actual" : "$expect" >/dev/null; then + return + fi + + # This is a multi-line message, which may in turn contain multi-line + # output, so let's format it ourself, readably + local -a actual_split + readarray -t actual_split <<<"$actual" + printf "#/vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n" >&2 + printf "#| FAIL: $testname\n" >&2 + printf "#| expected: '%s'\n" "$expect" >&2 + printf "#| actual: '%s'\n" "${actual_split[0]}" >&2 + local line + for line in "${actual_split[@]:1}"; do + printf "#| > '%s'\n" "$line" >&2 + done + printf "#\\^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" >&2 + false +} + + +############ +# dprint # conditional debug message +############ +# +# Set PODMAN_TEST_DEBUG to the name of one or more functions you want to debug +# +# Examples: +# +# $ PODMAN_TEST_DEBUG=parse_table bats . +# $ PODMAN_TEST_DEBUG="test_podman_images test_podman_run" bats . +# +function dprint() { + test -z "$PODMAN_TEST_DEBUG" && return + + caller="${FUNCNAME[1]}" + + # PODMAN_TEST_DEBUG is a space-separated list of desired functions + # e.g. "parse_table test_podman_images" (or even just "table") + for want in $PODMAN_TEST_DEBUG; do + # Check if our calling function matches any of the desired strings + if expr "$caller" : ".*$want" >/dev/null; then + echo "# ${FUNCNAME[1]}() : $*" >&3 + return + fi + done +} + + +################# +# parse_table # Split a table on '|' delimiters; return space-separated +################# +# +# See sample .bats scripts for examples. The idea is to list a set of +# tests in a table, then use simple logic to iterate over each test. +# Columns are separated using '|' (pipe character) because sometimes +# we need spaces in our fields. +# +function parse_table() { + while read line; do + test -z "$line" && continue + + declare -a row=() + while read col; do + dprint "col=<<$col>>" + row+=("$col") + done < <(echo "$line" | tr '|' '\012' | sed -e 's/^ *//' -e 's/\\/\\\\/g') + + printf "%q " "${row[@]}" + printf "\n" + done <<<"$1" +} + + +################### +# random_string # Returns a pseudorandom human-readable string +################### +# +# Numeric argument, if present, is desired length of string +# +function random_string() { + local length=${1:-10} + + head /dev/urandom | tr -dc a-zA-Z0-9 | head -c$length +} + +# END miscellaneous tools +############################################################################### diff --git a/test/system/helpers.t b/test/system/helpers.t new file mode 100755 index 000000000..7b4e48a84 --- /dev/null +++ b/test/system/helpers.t @@ -0,0 +1,145 @@ +#!/bin/bash +# +# regression tests for helpers.bash +# +# Some of those helper functions are fragile, and we don't want to break +# anything if we have to mess with them. +# + +source $(dirname $0)/helpers.bash + +die() { + echo "$(basename $0): $*" >&2 + exit 1 +} + +# Iterator and return code; updated in check_result() +testnum=0 +rc=0 + +############################################################################### +# BEGIN test the parse_table helper + +function check_result { + testnum=$(expr $testnum + 1) + if [ "$1" = "$2" ]; then + echo "ok $testnum $3 = $1" + else + echo "not ok $testnum $3" + echo "# expected: $2" + echo "# actual: $1" + rc=1 + fi +} + +# IMPORTANT NOTE: you have to do +# this: while ... done < <(parse_table) +# and not: parse_table | while read ... +# +# ...because piping to 'while' makes it a subshell, hence testnum and rc +# will not be updated. +# +while read x y z; do + check_result "$x" "a" "parse_table simple: column 1" + check_result "$y" "b" "parse_table simple: column 2" + check_result "$z" "c" "parse_table simple: column 3" +done < <(parse_table "a | b | c") + +# More complicated example, with spaces +while read x y z; do + check_result "$x" "a b" "parse_table with spaces: column 1" + check_result "$y" "c d" "parse_table with spaces: column 2" + check_result "$z" "e f g" "parse_table with spaces: column 3" +done < <(parse_table "a b | c d | e f g") + +# Multi-row, with spaces and with blank lines +table=" +a | b | c d e +d e f | g h | i j +" +declare -A expect=( + [0,0]="a" + [0,1]="b" + [0,2]="c d e" + [1,0]="d e f" + [1,1]="g h" + [1,2]="i j" +) +row=0 +while read x y z;do + check_result "$x" "${expect[$row,0]}" "parse_table multi_row[$row,0]" + check_result "$y" "${expect[$row,1]}" "parse_table multi_row[$row,1]" + check_result "$z" "${expect[$row,2]}" "parse_table multi_row[$row,2]" + row=$(expr $row + 1) +done < <(parse_table "$table") + +# Backslash handling. The first element should have none, the second some +while read x y;do + check_result "$x" '[0-9]{2}' "backslash test - no backslashes" + check_result "$y" '[0-9]\{3\}' "backslash test - one backslash each" +done < <(parse_table "[0-9]{2} | [0-9]\\\{3\\\}") + +# Empty strings. I wish we could convert those to real empty strings. +while read x y z; do + check_result "$x" "''" "empty string - left-hand" + check_result "$y" "''" "empty string - middle" + check_result "$z" "''" "empty string - right" +done < <(parse_table " | |") + +# Quotes +while read x y z;do + check_result "$x" "a 'b c'" "single quotes" + check_result "$y" "d \"e f\" g" "double quotes" + check_result "$z" "h" "no quotes" + + # FIXME FIXME FIXME: this is the only way I can find to get bash-like + # splitting of tokens. It really should be done inside parse_table + # but I can't find any way of doing so. If you can find a way, please + # update this test and any BATS tests that rely on quoting. + eval set "$x" + check_result "$1" "a" "single quotes - token split - 1" + check_result "$2" "b c" "single quotes - token split - 2" + check_result "$3" "" "single quotes - token split - 3" + + eval set "$y" + check_result "$1" "d" "double quotes - token split - 1" + check_result "$2" "e f" "double quotes - token split - 2" + check_result "$3" "g" "double quotes - token split - 3" +done < <(parse_table "a 'b c' | d \"e f\" g | h") + +# END test the parse_table helper +############################################################################### +# BEGIN dprint + +function dprint_test_1() { + dprint "$*" +} + +# parse_table works, might as well use it +# +# <value of PODMAN_TEST_DEBUG> | <blank for no msg, - for msg> | <desc> +# +table=" + | | debug unset +dprint_test | - | substring match +dprint_test_1 | - | exact match +dprint_test_10 | | caller name mismatch +xxx yyy zzz | | multiple callers, no match +dprint_test_1 xxx yyy zzz | - | multiple callers, match at start +xxx dprint_test_1 yyy zzz | - | multiple callers, match in middle +xxx yyy zzz dprint_test_1 | - | multiple callers, match at end +" +while read var expect name; do + random_string=$(random_string 20) + PODMAN_TEST_DEBUG="$var" result=$(dprint_test_1 "$random_string" 3>&1) + expect_full="" + if [ -n "$expect" -a "$expect" != "''" ]; then + expect_full="# dprint_test_1() : $random_string" + fi + check_result "$result" "$expect_full" "DEBUG='$var' - $name" +done < <(parse_table "$table") + +# END dprint +############################################################################### + +exit $rc diff --git a/test/system/libpod_suite_test.go b/test/system/libpod_suite_test.go deleted file mode 100644 index 5de50e4e7..000000000 --- a/test/system/libpod_suite_test.go +++ /dev/null @@ -1,217 +0,0 @@ -package system - -import ( - "fmt" - "os" - "strings" - "testing" - - . "github.com/containers/libpod/test/utils" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var ( - PODMAN_BINARY string - GLOBALOPTIONS = []string{"--cgroup-manager", - "--cni-config-dir", - "--config", "-c", - "--conmon", - "--cpu-profile", - "--log-level", - "--root", - "--tmpdir", - "--runroot", - "--runtime", - "--storage-driver", - "--storage-opt", - "--syslog", - } - PODMAN_SUBCMD = []string{"attach", - "commit", - "container", - "build", - "create", - "diff", - "exec", - "export", - "history", - "image", - "images", - "import", - "info", - "inspect", - "kill", - "load", - "login", - "logout", - "logs", - "mount", - "pause", - "ps", - "pod", - "port", - "pull", - "push", - "restart", - "rm", - "rmi", - "run", - "save", - "search", - "start", - "stats", - "stop", - "tag", - "top", - "umount", - "unpause", - "version", - "wait", - "h", - } - INTEGRATION_ROOT string - ARTIFACT_DIR = "/tmp/.artifacts" - ALPINE = "docker.io/library/alpine:latest" - BB = "docker.io/library/busybox:latest" - BB_GLIBC = "docker.io/library/busybox:glibc" - fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest" - nginx = "quay.io/baude/alpine_nginx:latest" - redis = "docker.io/library/redis:alpine" - registry = "docker.io/library/registry:2" - infra = "k8s.gcr.io/pause:3.1" - defaultWaitTimeout = 90 -) - -// PodmanTestSystem struct for command line options -type PodmanTestSystem struct { - PodmanTest - GlobalOptions map[string]string - PodmanCmdOptions map[string][]string -} - -// TestLibpod ginkgo master function -func TestLibpod(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Libpod Suite") -} - -var _ = BeforeSuite(func() { -}) - -// PodmanTestCreate creates a PodmanTestSystem instance for the tests -func PodmanTestCreate(tempDir string) *PodmanTestSystem { - var envKey string - globalOptions := make(map[string]string) - podmanCmdOptions := make(map[string][]string) - - for _, n := range GLOBALOPTIONS { - envKey = strings.Replace(strings.ToUpper(strings.Trim(n, "-")), "-", "_", -1) - if isEnvSet(envKey) { - globalOptions[n] = os.Getenv(envKey) - } - } - - for _, n := range PODMAN_SUBCMD { - envKey = strings.Replace("PODMAN_SUBCMD_OPTIONS", "SUBCMD", strings.ToUpper(n), -1) - if isEnvSet(envKey) { - podmanCmdOptions[n] = strings.Split(os.Getenv(envKey), " ") - } - } - - podmanBinary := "podman" - if os.Getenv("PODMAN_BINARY") != "" { - podmanBinary = os.Getenv("PODMAN_BINARY") - } - - p := &PodmanTestSystem{ - PodmanTest: PodmanTest{ - PodmanBinary: podmanBinary, - ArtifactPath: ARTIFACT_DIR, - TempDir: tempDir, - }, - GlobalOptions: globalOptions, - PodmanCmdOptions: podmanCmdOptions, - } - - p.PodmanMakeOptions = p.makeOptions - - return p -} - -func (p *PodmanTestSystem) Podman(args []string) *PodmanSession { - return p.PodmanBase(args) -} - -//MakeOptions assembles all the podman options -func (p *PodmanTestSystem) makeOptions(args []string) []string { - var addOptions, subArgs []string - for _, n := range GLOBALOPTIONS { - if p.GlobalOptions[n] != "" { - addOptions = append(addOptions, n, p.GlobalOptions[n]) - } - } - - if len(args) == 0 { - return addOptions - } - - subCmd := args[0] - addOptions = append(addOptions, subCmd) - if subCmd == "unmount" { - subCmd = "umount" - } - if subCmd == "help" { - subCmd = "h" - } - - if _, ok := p.PodmanCmdOptions[subCmd]; ok { - m := make(map[string]bool) - subArgs = p.PodmanCmdOptions[subCmd] - for i := 0; i < len(subArgs); i++ { - m[subArgs[i]] = true - } - for i := 1; i < len(args); i++ { - if _, ok := m[args[i]]; !ok { - subArgs = append(subArgs, args[i]) - } - } - } else { - subArgs = args[1:] - } - - addOptions = append(addOptions, subArgs...) - - return addOptions -} - -// Cleanup cleans up the temporary store -func (p *PodmanTestSystem) Cleanup() { - // Remove all containers - stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) - stopall.WaitWithDefaultTimeout() - - session := p.Podman([]string{"rm", "-fa"}) - session.Wait(90) - // Nuke tempdir - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } -} - -// CleanupPod cleans up the temporary store -func (p *PodmanTestSystem) CleanupPod() { - // Remove all containers - session := p.Podman([]string{"pod", "rm", "-fa"}) - session.Wait(90) - // Nuke tempdir - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } -} - -// Check if the key is set in Env -func isEnvSet(key string) bool { - _, set := os.LookupEnv(key) - return set -} diff --git a/test/system/version_test.go b/test/system/version_test.go deleted file mode 100644 index ada0093b7..000000000 --- a/test/system/version_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package system - -import ( - "fmt" - "os" - "regexp" - - . "github.com/containers/libpod/test/utils" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Podman version test", func() { - var ( - tempdir string - err error - podmanTest *PodmanTestSystem - ) - - BeforeEach(func() { - tempdir, err = CreateTempDirInTempDir() - if err != nil { - os.Exit(1) - } - podmanTest = PodmanTestCreate(tempdir) - }) - - AfterEach(func() { - podmanTest.Cleanup() - f := CurrentGinkgoTestDescription() - timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) - GinkgoWriter.Write([]byte(timedResult)) - }) - - It("Smoking test: podman version with extra args", func() { - logc := podmanTest.Podman([]string{"version", "anything", "-", "--"}) - logc.WaitWithDefaultTimeout() - Expect(logc.ExitCode()).To(Equal(0)) - ver := logc.OutputToString() - Expect(regexp.MatchString("Version:.*?Go Version:.*?OS/Arch", ver)).To(BeTrue()) - }) - - It("Negative test: podman version with extra flag", func() { - logc := podmanTest.Podman([]string{"version", "--foo"}) - logc.WaitWithDefaultTimeout() - Expect(logc.ExitCode()).NotTo(Equal(0)) - err, _ := logc.GrepString("Incorrect Usage: flag provided but not defined: -foo") - Expect(err).To(BeTrue()) - }) - -}) diff --git a/test/test_podman_baseline.sh b/test/test_podman_baseline.sh index 664fd2b03..5c24229bb 100755 --- a/test/test_podman_baseline.sh +++ b/test/test_podman_baseline.sh @@ -19,6 +19,7 @@ ####### # See if we want to stop on errors and/or install and then remove Docker. ####### +HOST_PORT="${HOST_PORT:-8080}" showerror=0 installdocker=0 usedocker=1 @@ -78,7 +79,99 @@ echo $image ######## # Run container and display contents in /etc ######## -podman run $image ls -alF /etc +podman run --rm $image ls -alF /etc + +######## +# Test networking, bind mounting a file, stdin/stdout redirect +######## +echo "Testing networking: ..." +port_test_failed=0 +txt1="Hello, Podman" +echo "$txt1" > /tmp/hello.txt +podman run -d --name myweb -p "$HOST_PORT:80" -w /var/www -v /tmp/hello.txt:/var/www/index.txt busybox httpd -f -p 80 +echo "$txt1" | podman exec -i myweb sh -c "cat > /var/www/index2.txt" +txt2=$( podman exec myweb cat /var/www/index2.txt ) +[ "x$txt1" == "x$txt2" ] && echo "PASS1" || { echo "FAIL1"; port_test_failed=1; } +txt2=$( podman run --rm --net host busybox wget -qO - http://localhost:$HOST_PORT/index.txt ) +[ "x$txt1" == "x$txt2" ] && echo "PASS2" || { echo "FAIL2"; port_test_failed=1; } +txt2=$( podman run --rm --net host busybox wget -qO - http://localhost:$HOST_PORT/index2.txt ) +[ "x$txt1" == "x$txt2" ] && echo "PASS3" || { echo "FAIL3"; port_test_failed=1; } +# podman run --rm --net container:myweb --add-host myweb:127.0.0.1 busybox wget -qO - http://myweb/index.txt +rm /tmp/hello.txt +podman stop myweb +podman rm myweb +[ "0$port_test_failed" -eq 1 ] && [ "0$showerror" -eq 1 ] && { + echo "networking test failed"; + exit -1; +} + + +######## +# pull and run many containers in parallel, test locks ..etc. +######## +prun_test_failed=0 +podman rmi docker.io/library/busybox:latest > /dev/null || : +for i in `seq 10` +do ( podman run -d --name b$i docker.io/library/busybox:latest busybox httpd -f -p 80 )& +done +echo -e "\nwaiting for creation...\n" +wait +echo -e "\ndone\n" +# assert we have 10 running containers +count=$( podman ps -q | wc -l ) +[ "x$count" == "x10" ] && echo "PASS" || { echo "FAIL, expecting 10 found $count"; prun_test_failed=1; } +[ "0$prun_test_failed" -eq 1 ] && [ "0$showerror" -eq 1 ] && { + echo "was expecting 10 running containers"; + exit -1; +} + +prun_test_failed=0 +for i in `seq 10`; do ( podman stop -t=1 b$i; podman rm b$i )& done +echo -e "\nwaiting for deletion...\n" +wait +echo -e "\ndone\n" +# assert we have 0 running containers +count=$( podman ps -q | wc -l ) +[ "x$count" == "x0" ] && echo "PASS" || { echo "FAIL, expecting 0 found $count"; prun_test_failed=1; } +[ "0$prun_test_failed" -eq 1 ] && [ "0$showerror" -eq 1 ] && { + echo "was expecting 0 running containers"; + exit -1; +} + + + +######## +# run many containers in parallel for an existing image, test locks ..etc. +######## +prun_test_failed=0 +podman pull docker.io/library/busybox:latest > /dev/null || : +for i in `seq 10` +do ( podman run -d --name c$i docker.io/library/busybox:latest busybox httpd -f -p 80 )& +done +echo -e "\nwaiting for creation...\n" +wait +echo -e "\ndone\n" +# assert we have 10 running containers +count=$( podman ps -q | wc -l ) +[ "x$count" == "x10" ] && echo "PASS" || { echo "FAIL, expecting 10 found $count"; prun_test_failed=1; } +[ "0$prun_test_failed" -eq 1 ] && [ "0$showerror" -eq 1 ] && { + echo "was expecting 10 running containers"; + exit -1; +} + + +for i in `seq 10`; do ( podman stop -t=1 c$i; podman rm c$i )& done +echo -e "\nwaiting for deletion...\n" +wait +echo -e "\ndone\n" +# assert we have 0 running containers +count=$( podman ps -q | wc -l ) +[ "x$count" == "x0" ] && echo "PASS" || { echo "FAIL, expecting 0 found $count"; prun_test_failed=1; } +[ "0$prun_test_failed" -eq 1 ] && [ "0$showerror" -eq 1 ] && { + echo "was expecting 0 running containers"; + exit -1; +} + ######## # Run Java in the container - should ERROR but never stop diff --git a/test/test_podman_build.sh b/test/test_podman_build.sh index 9faefc78a..39f1e784d 100644 --- a/test/test_podman_build.sh +++ b/test/test_podman_build.sh @@ -34,6 +34,13 @@ echo ######################################################## echo ######################################################## +echo test "build directory before other options create a tag" +echo ######################################################## +TARGET=tagged-image +podman build $HOME/test/build/from-scratch --quiet=True -t $TARGET +podman images | grep tagged-image + +echo ######################################################## echo test "build-preserve-subvolumes" echo ######################################################## TARGET=volume-image diff --git a/transfer.md b/transfer.md index c2d472f08..df91cdf21 100644 --- a/transfer.md +++ b/transfer.md @@ -44,6 +44,7 @@ There are other equivalents for these tools | `docker container`|[`podman container`](./docs/podman-container.1.md) | | `docker create` | [`podman create`](./docs/podman-create.1.md) | | `docker diff` | [`podman diff`](./docs/podman-diff.1.md) | +| `docker events` | [`podman events`](./docs/podman-events.1.md) | | `docker export` | [`podman export`](./docs/podman-export.1.md) | | `docker history` | [`podman history`](./docs/podman-history.1.md) | | `docker image` | [`podman image`](./docs/podman-image.1.md) | @@ -77,6 +78,7 @@ There are other equivalents for these tools | `docker volume prune` | [`podman volume prune`](./docs/podman-volume-prune.1.md) | | `docker volume rm` | [`podman volume rm`](./docs/podman-volume-rm.1.md) | | `docker system` | [`podman system`](./docs/podman-system.1.md) | +| `docker system df` | [`podman system df`](./docs/podman-system-df.1.md) | | `docker system prune` | [`podman system prune`](./docs/podman-system-prune.1.md) | | `docker system info` | [`podman system info`](./docs/podman-system-info.1.md) | | `docker wait` | [`podman wait`](./docs/podman-wait.1.md) | @@ -89,7 +91,6 @@ Those Docker commands currently do not have equivalents in `podman`: | Missing command | Description| | :--- | :--- | -| `docker events` || | `docker network` || | `docker node` || | `docker plugin` | podman does not support plugins. We recommend you use alternative OCI Runtimes or OCI Runtime Hooks to alter behavior of podman.| @@ -112,6 +113,7 @@ The following podman commands do not have a Docker equivalent: * [`podman container refresh`](/docs/podman-container-refresh.1.md) * [`podman container runlabel`](/docs/podman-container-runlabel.1.md) * [`podman container restore`](/docs/podman-container-restore.1.md) +* [`podman healthcheck run`](/docs/podman-healthcheck-run.1.md) * [`podman image exists`](./docs/podman-image-exists.1.md) * [`podman image sign`](./docs/podman-image-sign.1.md) * [`podman image trust`](./docs/podman-image-trust.1.md) diff --git a/troubleshooting.md b/troubleshooting.md index 24a1dc6cb..08d79723a 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -39,7 +39,7 @@ error pulling image "fedora": unable to pull fedora: error getting default regis #### Solution - * Verify that the `/etc/containers/registries.conf` file exists. If not, verify that the skopeo-containers package is installed. + * Verify that the `/etc/containers/registries.conf` file exists. If not, verify that the containers-common package is installed. * Verify that the entries in the `[registries.search]` section of the /etc/containers/registries.conf file are valid and reachable. * i.e. `registries = ['registry.fedoraproject.org', 'quay.io', 'registry.access.redhat.com']` @@ -210,18 +210,17 @@ cannot find newuidmap: exec: "newuidmap": executable file not found in $PATH Install a version of shadow-utils that includes these executables. Note RHEL7 and Centos 7 will not have support for this until RHEL7.7 is released. -### 10) podman fails to run in user namespace because /etc/subuid is not properly populated. +### 10) rootless setup user: invalid argument Rootless podman requires the user running it to have a range of UIDs listed in /etc/subuid and /etc/subgid. #### Symptom -If you are running podman or buildah as a user, you get an error complaining about -a missing subuid ranges in /etc/subuid. +An user, either via --user or through the default configured for the image, is not mapped inside the namespace. ``` -podman run -ti fedora sh -No subuid ranges found for user "johndoe" in /etc/subuid +podman run --rm -ti --user 1000000 alpine echo hi +Error: container create failed: container_linux.go:344: starting container process caused "setup user: invalid argument" ``` #### Solution @@ -254,3 +253,60 @@ grep johndoe /etc/subuid /etc/subgid /etc/subuid:johndoe:200000:1001 /etc/subgid:johndoe:200000:1001 ``` + +### 11) Changing the location of the Graphroot leads to permission denied + +When I change the graphroot storage location in storage.conf, the next time I +run podman I get an error like: + +``` +# podman run -p 5000:5000 -it centos bash + +bash: error while loading shared libraries: /lib64/libc.so.6: cannot apply additional memory protection after relocation: Permission denied +``` + +For example, the admin sets up a spare disk to be mounted at `/src/containers`, +and points storage.conf at this directory. + + +#### Symptom + +SELinux blocks containers from using random locations for overlay storage. +These directories need to be labeled with the same labels as if the content was +under /var/lib/containers/storage. + +#### Solution + +Tell SELinux about the new containers storage by setting up an equivalence record. +This tells SELinux to label content under the new path, as if it was stored +under `/var/lib/containers/storage`. + +``` +semanage fcontext -a -e /var/lib/containers /srv/containers +restorecon -R -v /src/containers +``` + +The semanage command above tells SELinux to setup the default labeling of +`/srv/containers` to match `/var/lib/containers`. The `restorecon` command +tells SELinux to apply the labels to the actual content. + +Now all new content created in these directories will automatically be created +with the correct label. + +### 12) Running Podman inside a container causes container crashes and inconsistent states + +Running Podman in a container and forwarding some, but not all, of the required host directories can cause inconsistent container behavior. + +#### Symptom + +After creating a container with Podman's storage directories mounted in from the host and running Podman inside a container, all containers show their state as "configured" or "created", even if they were running or stopped. + +#### Solution + +When running Podman inside a container, it is recommended to mount at a minimum `/var/lib/containers/storage/` as a volume. +Typically, you will not mount in the host version of the directory, but if you wish to share containers with the host, you can do so. +If you do mount in the host's `/var/lib/containers/storage`, however, you must also mount in the host's `/var/run/libpod` and `/var/run/containers/storage` directories. +Not doing this will cause Podman in the container to detect that temporary files have been cleared, leading it to assume a system restart has taken place. +This can cause Podman to reset container states and lose track of running containers. + +For running containers on the host from inside a container, we also recommend the [Podman remote client](remote_client.md), which only requires a single socket to be mounted into the container. diff --git a/utils/utils.go b/utils/utils.go index 33b0eb1c5..c195daa5d 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -93,7 +93,7 @@ var ErrDetach = errors.New("detached from container") // CopyDetachable is similar to io.Copy but support a detach key sequence to break out. func CopyDetachable(dst io.Writer, src io.Reader, keys []byte) (written int64, err error) { if len(keys) == 0 { - // Default keys : ctrl-p ctrl-q + // Default keys : ctrl-p,ctrl-q keys = []byte{16, 17} } diff --git a/vendor.conf b/vendor.conf index f739c76f4..bbfc96377 100644 --- a/vendor.conf +++ b/vendor.conf @@ -20,16 +20,17 @@ github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 github.com/containers/storage v1.10 -github.com/containers/psgo v1.1 +github.com/containers/psgo v1.2 github.com/coreos/go-systemd v14 github.com/cri-o/ocicni 2d2983e40c242322a56c22a903785e7f83eb378c github.com/cyphar/filepath-securejoin v0.2.1 github.com/davecgh/go-spew v1.1.0 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 -github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 +github.com/docker/docker 54dddadc7d5d89fe0be88f76979f6f6ab0dede83 github.com/docker/docker-credential-helpers v0.6.1 github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.3.2 +github.com/docker/libnetwork 5f7a3f68c3d9696229cdc09b8cb3d84c06b13e4e github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20 github.com/docker/spdystream 6480d4af844c189cf5dd913db24ddd339d3a4f85 github.com/fatih/camelcase v1.0.0 @@ -93,13 +94,11 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199 -github.com/containers/buildah v1.7.1 +github.com/containers/buildah 3ba8822d309128f7d76599432b8d9cdf77d4032f # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 -# do not go beyond the below commit as the next one requires a more recent -# docker which is in conflict with openshift/imagebuilder -github.com/fsouza/go-dockerclient 29c1814d12c072344bb91aac5d2ff719db39c523 -github.com/openshift/imagebuilder 36823496a6868f72bc36282cc475eb8a070c0934 +github.com/fsouza/go-dockerclient v1.3.0 +github.com/openshift/imagebuilder 705fe9255c57f8505efb9723a9ac4082b67973bc github.com/ulikunitz/xz v0.5.5 github.com/coreos/go-iptables v0.4.0 github.com/google/shlex c34317bd91bf98fab745d77b03933cf8769299fe diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index cca80a308..8f5364632 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -26,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.7.1" + Version = "1.8-dev" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/imagebuildah/build.go b/vendor/github.com/containers/buildah/imagebuildah/build.go index d69eab52f..f50b11f6c 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/build.go +++ b/vendor/github.com/containers/buildah/imagebuildah/build.go @@ -27,11 +27,11 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/stringid" - "github.com/docker/docker/builder/dockerfile/parser" docker "github.com/fsouza/go-dockerclient" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/openshift/imagebuilder" + "github.com/openshift/imagebuilder/dockerfile/parser" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -293,7 +293,7 @@ func (b *Executor) Preserve(path string) error { // Try and resolve the symlink (if one exists) // Set archivedPath and path based on whether a symlink is found or not - if symLink, err := ResolveSymLink(b.mountPoint, path); err == nil { + if symLink, err := resolveSymlink(b.mountPoint, path); err == nil { archivedPath = filepath.Join(b.mountPoint, symLink) path = symLink } else { @@ -510,20 +510,21 @@ func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error { stdin = devNull } options := buildah.RunOptions{ - Hostname: config.Hostname, - Runtime: b.runtime, - Args: b.runtimeArgs, - NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "", - Mounts: convertMounts(b.transientMounts), - Env: config.Env, - User: config.User, - WorkingDir: config.WorkingDir, - Entrypoint: config.Entrypoint, - Cmd: config.Cmd, - Stdin: stdin, - Stdout: b.out, - Stderr: b.err, - Quiet: b.quiet, + Hostname: config.Hostname, + Runtime: b.runtime, + Args: b.runtimeArgs, + NoPivot: os.Getenv("BUILDAH_NOPIVOT") != "", + Mounts: convertMounts(b.transientMounts), + Env: config.Env, + User: config.User, + WorkingDir: config.WorkingDir, + Entrypoint: config.Entrypoint, + Cmd: config.Cmd, + Stdin: stdin, + Stdout: b.out, + Stderr: b.err, + Quiet: b.quiet, + NamespaceOptions: b.namespaceOptions, } if config.NetworkDisabled { options.ConfigureNetwork = buildah.NetworkDisabled diff --git a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go index 6feedf6a5..86bf7653b 100644 --- a/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go +++ b/vendor/github.com/containers/buildah/imagebuildah/chroot_symlink.go @@ -24,9 +24,7 @@ func init() { reexec.Register(symlinkModifiedTime, resolveSymlinkTimeModified) } -// main() for grandparent subprocess. Its main job is to shuttle stdio back -// and forth, managing a pseudo-terminal if we want one, for our child, the -// parent subprocess. +// main() for resolveSymlink()'s subprocess. func resolveChrootedSymlinks() { status := 0 flag.Parse() @@ -57,9 +55,9 @@ func resolveChrootedSymlinks() { os.Exit(status) } -// ResolveSymLink (in the grandparent process) resolves any symlink in filename +// resolveSymlink uses a child subprocess to resolve any symlinks in filename // in the context of rootdir. -func ResolveSymLink(rootdir, filename string) (string, error) { +func resolveSymlink(rootdir, filename string) (string, error) { // The child process expects a chroot and one path that // will be consulted relative to the chroot directory and evaluated // for any symbolic links present. @@ -253,7 +251,7 @@ func hasSymlink(path string) (bool, string, error) { } // if the symlink points to a relative path, prepend the path till now to the resolved path if !filepath.IsAbs(targetDir) { - targetDir = filepath.Join(path, targetDir) + targetDir = filepath.Join(filepath.Dir(path), targetDir) } // run filepath.Clean to remove the ".." from relative paths return true, filepath.Clean(targetDir), nil diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 768cdd0c6..262c90220 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -22,27 +22,23 @@ const ( // BaseImageFakeName is the "name" of a source image which we interpret // as "no image". BaseImageFakeName = imagebuilder.NoBaseImageSpecifier - - // minimumTruncatedIDLength is the minimum length of an identifier that - // we'll accept as possibly being a truncated image ID. - minimumTruncatedIDLength = 3 ) -func pullAndFindImage(ctx context.Context, store storage.Store, transport string, imageName string, options BuilderOptions, sc *types.SystemContext) (*storage.Image, types.ImageReference, error) { +func pullAndFindImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options BuilderOptions, sc *types.SystemContext) (*storage.Image, types.ImageReference, error) { pullOptions := PullOptions{ ReportWriter: options.ReportWriter, Store: store, SystemContext: options.SystemContext, BlobDirectory: options.PullBlobDirectory, } - ref, err := pullImage(ctx, store, transport, imageName, pullOptions, sc) + ref, err := pullImage(ctx, store, srcRef, pullOptions, sc) if err != nil { - logrus.Debugf("error pulling image %q: %v", imageName, err) + logrus.Debugf("error pulling image %q: %v", transports.ImageName(srcRef), err) return nil, nil, err } img, err := is.Transport.GetStoreImage(store, ref) if err != nil { - logrus.Debugf("error reading pulled image %q: %v", imageName, err) + logrus.Debugf("error reading pulled image %q: %v", transports.ImageName(srcRef), err) return nil, nil, errors.Wrapf(err, "error locating image %q in local storage", transports.ImageName(ref)) } return img, ref, nil @@ -112,56 +108,45 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store failures := []failure{} for _, image := range candidates { - var err error - if len(image) >= minimumTruncatedIDLength { - if img, err := store.Image(image); err == nil && img != nil && strings.HasPrefix(img.ID, image) { - ref, err := is.Transport.ParseStoreReference(store, img.ID) - if err != nil { - return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) - } - return ref, transport, img, nil - } - } - - if options.PullPolicy == PullAlways { - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, transport, image, options, systemContext) + if transport == "" { + img, err := store.Image(image) if err != nil { - logrus.Debugf("unable to pull and read image %q: %v", image, err) + logrus.Debugf("error looking up known-local image %q: %v", image, err) failures = append(failures, failure{resolvedImageName: image, err: err}) continue } - return pulledReference, transport, pulledImg, nil + ref, err := is.Transport.ParseStoreReference(store, img.ID) + if err != nil { + return nil, "", nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) + } + return ref, transport, img, nil } - srcRef, err := alltransports.ParseImageName(image) + trans := transport + if transport != util.DefaultTransport { + trans = trans + ":" + } + srcRef, err := alltransports.ParseImageName(trans + image) if err != nil { - if transport == "" { - logrus.Debugf("error parsing image name %q: %v", image, err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Wrapf(err, "error parsing image name"), - }) - continue - } - logrus.Debugf("error parsing image name %q as given, trying with transport %q: %v", image, transport, err) + logrus.Debugf("error parsing image name %q: %v", trans+image, err) + failures = append(failures, failure{ + resolvedImageName: image, + err: errors.Wrapf(err, "error parsing attempted image name %q", trans+image), + }) + continue + } - trans := transport - if transport != util.DefaultTransport { - trans = trans + ":" - } - srcRef2, err := alltransports.ParseImageName(trans + image) + if options.PullPolicy == PullAlways { + pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) if err != nil { - logrus.Debugf("error parsing image name %q: %v", transport+image, err) - failures = append(failures, failure{ - resolvedImageName: image, - err: errors.Wrapf(err, "error parsing attempted image name %q", transport+image), - }) + logrus.Debugf("unable to pull and read image %q: %v", image, err) + failures = append(failures, failure{resolvedImageName: image, err: err}) continue } - srcRef = srcRef2 + return pulledReference, transport, pulledImg, nil } - destImage, err := localImageNameForReference(ctx, store, srcRef, options.FromImage) + destImage, err := localImageNameForReference(ctx, store, srcRef) if err != nil { return nil, "", nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) } @@ -187,7 +172,7 @@ func resolveImage(ctx context.Context, systemContext *types.SystemContext, store continue } - pulledImg, pulledReference, err := pullAndFindImage(ctx, store, transport, image, options, systemContext) + pulledImg, pulledReference, err := pullAndFindImage(ctx, store, srcRef, options, systemContext) if err != nil { logrus.Debugf("unable to pull and read image %q: %v", image, err) failures = append(failures, failure{resolvedImageName: image, err: err}) diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index f167353b8..da07545c7 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -156,7 +156,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.StringVar(&flags.Runtime, "runtime", util.Runtime(), "`path` to an alternate runtime. Use BUILDAH_RUNTIME environment variable to override.") fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") - fs.BoolVar(&flags.Squash, "squash", false, "Squash newly built layers into a single new layer. The build process does not currently support caching so this is a NOOP.") + fs.BoolVar(&flags.Squash, "squash", false, "Squash newly built layers into a single new layer.") fs.StringSliceVarP(&flags.Tag, "tag", "t", []string{}, "tagged `name` to apply to the built image") fs.StringVar(&flags.Target, "target", "", "set the target build stage to build") fs.BoolVar(&flags.TlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") diff --git a/cmd/podman/formats/formats.go b/vendor/github.com/containers/buildah/pkg/formats/formats.go index 37f9b8a20..37f9b8a20 100644 --- a/cmd/podman/formats/formats.go +++ b/vendor/github.com/containers/buildah/pkg/formats/formats.go diff --git a/cmd/podman/formats/templates.go b/vendor/github.com/containers/buildah/pkg/formats/templates.go index c2582552a..c2582552a 100644 --- a/cmd/podman/formats/templates.go +++ b/vendor/github.com/containers/buildah/pkg/formats/templates.go diff --git a/vendor/github.com/containers/buildah/pkg/parse/parse.go b/vendor/github.com/containers/buildah/pkg/parse/parse.go index a26d15631..c309f686a 100644 --- a/vendor/github.com/containers/buildah/pkg/parse/parse.go +++ b/vendor/github.com/containers/buildah/pkg/parse/parse.go @@ -9,6 +9,7 @@ import ( "github.com/spf13/cobra" "net" "os" + "os/exec" "path/filepath" "strconv" "strings" @@ -319,7 +320,7 @@ func getDockerAuth(creds string) (*types.DockerAuthConfig, error) { } // IDMappingOptions parses the build options related to user namespaces and ID mapping. -func IDMappingOptions(c *cobra.Command) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) { +func IDMappingOptions(c *cobra.Command, isolation buildah.Isolation) (usernsOptions buildah.NamespaceOptions, idmapOptions *buildah.IDMappingOptions, err error) { user := c.Flag("userns-uid-map-user").Value.String() group := c.Flag("userns-gid-map-group").Value.String() // If only the user or group was specified, use the same value for the @@ -391,11 +392,26 @@ func IDMappingOptions(c *cobra.Command) (usernsOptions buildah.NamespaceOptions, if len(gidmap) == 0 && len(uidmap) != 0 { gidmap = uidmap } + + useSlirp4netns := false + + if isolation == buildah.IsolationOCIRootless { + _, err := exec.LookPath("slirp4netns") + if execerr, ok := err.(*exec.Error); ok && !strings.Contains(execerr.Error(), "not found") { + return nil, nil, errors.Wrapf(err, "cannot lookup slirp4netns %v", execerr) + } + if err == nil { + useSlirp4netns = true + } else { + logrus.Warningf("could not find slirp4netns. Using host network namespace") + } + } + // By default, having mappings configured means we use a user // namespace. Otherwise, we don't. usernsOption := buildah.NamespaceOption{ Name: string(specs.UserNamespace), - Host: len(uidmap) == 0 && len(gidmap) == 0, + Host: len(uidmap) == 0 && len(gidmap) == 0 && !useSlirp4netns, } // If the user specifically requested that we either use or don't use // user namespaces, override that default. diff --git a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go index 242953609..3b64f8952 100644 --- a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go +++ b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage/pkg/idtools" rspec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" @@ -176,7 +177,7 @@ func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPre // Add FIPS mode secret if /etc/system-fips exists on the host _, err := os.Stat("/etc/system-fips") if err == nil { - if err := addFIPSModeSecret(&secretMounts, containerWorkingDir); err != nil { + if err := addFIPSModeSecret(&secretMounts, containerWorkingDir, mountPrefix, mountLabel, uid, gid); err != nil { logrus.Errorf("error adding FIPS mode secret to container: %v", err) } } else if os.IsNotExist(err) { @@ -264,13 +265,16 @@ func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir, mountPr // root filesystem if /etc/system-fips exists on hosts. // This enables the container to be FIPS compliant and run openssl in // FIPS mode as the host is also in FIPS mode. -func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir string) error { +func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir, mountPrefix, mountLabel string, uid, gid int) error { secretsDir := "/run/secrets" ctrDirOnHost := filepath.Join(containerWorkingDir, secretsDir) if _, err := os.Stat(ctrDirOnHost); os.IsNotExist(err) { - if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { + if err = idtools.MkdirAllAs(ctrDirOnHost, 0755, uid, gid); err != nil { return errors.Wrapf(err, "making container directory on host failed") } + if err = label.Relabel(ctrDirOnHost, mountLabel, false); err != nil { + return errors.Wrap(err, "error applying correct labels") + } } fipsFile := filepath.Join(ctrDirOnHost, "system-fips") // In the event of restart, it is possible for the FIPS mode file to already exist @@ -284,7 +288,7 @@ func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir string) error if !mountExists(*mounts, secretsDir) { m := rspec.Mount{ - Source: ctrDirOnHost, + Source: filepath.Join(mountPrefix, secretsDir), Destination: secretsDir, Type: "bind", Options: []string{"bind", "rprivate"}, diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index 363cf5ce2..d3c9870af 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -19,10 +19,9 @@ import ( "github.com/containers/image/signature" is "github.com/containers/image/storage" "github.com/containers/image/transports" - "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage" - "github.com/hashicorp/go-multierror" + multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -52,15 +51,14 @@ type PullOptions struct { AllTags bool } -func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference, spec string) (string, error) { +func localImageNameForReference(ctx context.Context, store storage.Store, srcRef types.ImageReference) (string, error) { if srcRef == nil { return "", errors.Errorf("reference to image is empty") } - split := strings.SplitN(spec, ":", 2) - file := split[len(split)-1] var name string switch srcRef.Transport().Name() { case dockerarchive.Transport.Name(): + file := srcRef.StringWithinTransport() tarSource, err := tarfile.NewSourceFromFile(file) if err != nil { return "", errors.Wrapf(err, "error opening tarfile %q as a source image", file) @@ -104,14 +102,15 @@ func localImageNameForReference(ctx context.Context, store storage.Store, srcRef } case directory.Transport.Name(): // supports pull from a directory - name = split[1] + name = srcRef.StringWithinTransport() // remove leading "/" if name[:1] == "/" { name = name[1:] } case oci.Transport.Name(): // supports pull from a directory - name = split[1] + split := strings.SplitN(srcRef.StringWithinTransport(), ":", 2) + name = split[0] // remove leading "/" if name[:1] == "/" { name = name[1:] @@ -175,21 +174,29 @@ func Pull(ctx context.Context, imageName string, options PullOptions) error { return errors.New("Non-docker transport is not supported, for --all-tags pulling") } - spec := transport + storageRef.DockerReference().Name() - storageRef, err = alltransports.ParseImageName(spec) + repo := reference.TrimNamed(storageRef.DockerReference()) + dockerRef, err := docker.NewReference(reference.TagNameOnly(storageRef.DockerReference())) if err != nil { - return errors.Wrapf(err, "error getting repository tags") + return errors.Wrapf(err, "internal error creating docker.Transport reference for %s", storageRef.DockerReference().String()) } - tags, err := docker.GetRepositoryTags(ctx, systemContext, storageRef) + tags, err := docker.GetRepositoryTags(ctx, systemContext, dockerRef) if err != nil { return errors.Wrapf(err, "error getting repository tags") } for _, tag := range tags { - name := spec + ":" + tag + tagged, err := reference.WithTag(repo, tag) + if err != nil { + errs = multierror.Append(errs, err) + continue + } + taggedRef, err := docker.NewReference(tagged) + if err != nil { + return errors.Wrapf(err, "internal error creating docker.Transport reference for %s", tagged.String()) + } if options.ReportWriter != nil { - options.ReportWriter.Write([]byte("Pulling " + name + "\n")) + options.ReportWriter.Write([]byte("Pulling " + tagged.String() + "\n")) } - ref, err := pullImage(ctx, options.Store, transport, name, options, systemContext) + ref, err := pullImage(ctx, options.Store, taggedRef, options, systemContext) if err != nil { errs = multierror.Append(errs, err) continue @@ -208,27 +215,7 @@ func Pull(ctx context.Context, imageName string, options PullOptions) error { return errs.ErrorOrNil() } -func pullImage(ctx context.Context, store storage.Store, transport string, imageName string, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { - spec := imageName - srcRef, err := alltransports.ParseImageName(spec) - if err != nil { - logrus.Debugf("error parsing image name %q, trying with transport %q: %v", spec, transport, err) - if transport == "" { - transport = util.DefaultTransport - } else { - if transport != util.DefaultTransport { - transport = transport + ":" - } - } - spec = transport + spec - srcRef2, err2 := alltransports.ParseImageName(spec) - if err2 != nil { - return nil, errors.Wrapf(err2, "error parsing image name %q", spec) - } - srcRef = srcRef2 - } - logrus.Debugf("parsed image name %q", spec) - +func pullImage(ctx context.Context, store storage.Store, srcRef types.ImageReference, options PullOptions, sc *types.SystemContext) (types.ImageReference, error) { blocked, err := isReferenceBlocked(srcRef, sc) if err != nil { return nil, errors.Wrapf(err, "error checking if pulling from registry for %q is blocked", transports.ImageName(srcRef)) @@ -237,7 +224,7 @@ func pullImage(ctx context.Context, store storage.Store, transport string, image return nil, errors.Errorf("pull access to registry for %q is blocked by configuration", transports.ImageName(srcRef)) } - destName, err := localImageNameForReference(ctx, store, srcRef, spec) + destName, err := localImageNameForReference(ctx, store, srcRef) if err != nil { return nil, errors.Wrapf(err, "error computing local image name for %q", transports.ImageName(srcRef)) } @@ -274,9 +261,9 @@ func pullImage(ctx context.Context, store storage.Store, transport string, image } }() - logrus.Debugf("copying %q to %q", spec, destName) + logrus.Debugf("copying %q to %q", transports.ImageName(srcRef), destName) if _, err := cp.Image(ctx, policyContext, maybeCachedDestRef, srcRef, getCopyOptions(options.ReportWriter, srcRef, sc, maybeCachedDestRef, nil, "")); err != nil { - logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", spec, destName, err) + logrus.Debugf("error copying src image [%q] to dest image [%q] err: %v", transports.ImageName(srcRef), destName, err) return nil, err } return destRef, nil diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 4d6d28380..2fa3cd572 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -1,7 +1,6 @@ package buildah import ( - "bufio" "bytes" "encoding/json" "fmt" @@ -272,36 +271,6 @@ func addRlimits(ulimit []string, g *generate.Generator) error { return nil } -func addHosts(hosts []string, w io.Writer) error { - buf := bufio.NewWriter(w) - for _, host := range hosts { - values := strings.SplitN(host, ":", 2) - if len(values) != 2 { - return errors.Errorf("unable to parse host entry %q: incorrect format", host) - } - if values[0] == "" { - return errors.Errorf("hostname in host entry %q is empty", host) - } - if values[1] == "" { - return errors.Errorf("IP address in host entry %q is empty", host) - } - fmt.Fprintf(buf, "%s\t%s\n", values[1], values[0]) - } - return buf.Flush() -} - -func addHostsToFile(hosts []string, filename string) error { - if len(hosts) == 0 { - return nil - } - file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, os.ModeAppend) - if err != nil { - return errors.Wrapf(err, "error creating hosts file %q", filename) - } - defer file.Close() - return addHosts(hosts, file) -} - func addCommonOptsToSpec(commonOpts *CommonBuildOptions, g *generate.Generator) error { // Resources - CPU if commonOpts.CPUPeriod != 0 { @@ -638,6 +607,59 @@ func (b *Builder) addNetworkConfig(rdir, hostPath string, chownOpts *idtools.IDP return cfile, nil } +// generateHosts creates a containers hosts file +func (b *Builder) generateHosts(rdir, hostname string, addHosts []string, chownOpts *idtools.IDPair) (string, error) { + hostPath := "/etc/hosts" + stat, err := os.Stat(hostPath) + if err != nil { + return "", errors.Wrapf(err, "error statting %q for container %q", hostPath, b.ContainerID) + } + + hosts := bytes.NewBufferString("# Generated by Buildah\n") + orig, err := ioutil.ReadFile(hostPath) + if err != nil { + return "", errors.Wrapf(err, "unable to read %s", hostPath) + } + hosts.Write(orig) + for _, host := range addHosts { + // verify the host format + values := strings.SplitN(host, ":", 2) + if len(values) != 2 { + return "", errors.Errorf("unable to parse host entry %q: incorrect format", host) + } + if values[0] == "" { + return "", errors.Errorf("hostname in host entry %q is empty", host) + } + if values[1] == "" { + return "", errors.Errorf("IP address in host entry %q is empty", host) + } + hosts.Write([]byte(fmt.Sprintf("%s\t%s\n", values[1], values[0]))) + } + + if hostname != "" { + hosts.Write([]byte(fmt.Sprintf("127.0.0.1 %s\n", hostname))) + hosts.Write([]byte(fmt.Sprintf("::1 %s\n", hostname))) + } + cfile := filepath.Join(rdir, filepath.Base(hostPath)) + if err = ioutils.AtomicWriteFile(cfile, hosts.Bytes(), stat.Mode().Perm()); err != nil { + return "", errors.Wrapf(err, "error writing /etc/hosts into the container") + } + uid := int(stat.Sys().(*syscall.Stat_t).Uid) + gid := int(stat.Sys().(*syscall.Stat_t).Gid) + if chownOpts != nil { + uid = chownOpts.UID + gid = chownOpts.GID + } + if err = os.Chown(cfile, uid, gid); err != nil { + return "", errors.Wrapf(err, "error chowning file %q for container %q", cfile, b.ContainerID) + } + if err := label.Relabel(cfile, b.MountLabel, false); err != nil { + return "", errors.Wrapf(err, "error relabeling %q in container %q", cfile, b.ContainerID) + } + + return cfile, nil +} + func setupMaskedPaths(g *generate.Generator) { for _, mp := range []string{ "/proc/acpi", @@ -1081,15 +1103,11 @@ func (b *Builder) Run(command []string, options RunOptions) error { volumes := b.Volumes() if !contains(volumes, "/etc/hosts") { - hostFile, err := b.addNetworkConfig(path, "/etc/hosts", rootIDPair) + hostFile, err := b.generateHosts(path, spec.Hostname, b.CommonBuildOpts.AddHost, rootIDPair) if err != nil { return err } bindFiles["/etc/hosts"] = hostFile - - if err := addHostsToFile(b.CommonBuildOpts.AddHost, hostFile); err != nil { - return err - } } if !contains(volumes, "/etc/resolv.conf") { @@ -1747,7 +1765,9 @@ func runConfigureNetwork(isolation Isolation, options RunOptions, configureNetwo var netconf, undo []*libcni.NetworkConfigList if isolation == IsolationOCIRootless { - return setupRootlessNetwork(pid) + if ns := options.NamespaceOptions.Find(string(specs.NetworkNamespace)); ns != nil && !ns.Host { + return setupRootlessNetwork(pid) + } } // Scan for CNI configuration files. confdir := options.CNIConfigDir diff --git a/vendor/github.com/containers/buildah/util/util.go b/vendor/github.com/containers/buildah/util/util.go index d98493634..7f3bbaef4 100644 --- a/vendor/github.com/containers/buildah/util/util.go +++ b/vendor/github.com/containers/buildah/util/util.go @@ -20,7 +20,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" "github.com/docker/distribution/registry/api/errcode" - "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -47,6 +47,12 @@ var ( // correspond to in the set of configured registries, the transport used to // pull the image, and a boolean which is true iff // 1) the list of search registries was used, and 2) it was empty. +// +// The returned image names never include a transport: prefix, and if transport != "", +// (transport, image) should be a valid input to alltransports.ParseImageName. +// transport == "" indicates that image that already exists in a local storage, +// and the name is valid for store.Image() / storage.Transport.ParseStoreReference(). +// // NOTE: The "list of search registries is empty" check does not count blocked registries, // and neither the implied "localhost" nor a possible firstRegistry are counted func ResolveName(name string, firstRegistry string, sc *types.SystemContext, store storage.Store) ([]string, string, bool, error) { @@ -162,15 +168,7 @@ func ExpandNames(names []string, firstRegistry string, systemContext *types.Syst name = named } name = reference.TagNameOnly(name) - tag := "" - digest := "" - if tagged, ok := name.(reference.NamedTagged); ok { - tag = ":" + tagged.Tag() - } - if digested, ok := name.(reference.Digested); ok { - digest = "@" + digested.Digest().String() - } - expanded = append(expanded, name.Name()+tag+digest) + expanded = append(expanded, name.String()) } return expanded, nil } diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index 27bf45541..53c2e673e 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -9,14 +9,15 @@ github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 github.com/boltdb/bolt v1.3.1 github.com/containers/libpod v1.0 -github.com/containers/storage v1.10 +github.com/containers/storage v1.11 github.com/docker/distribution 5f6282db7d65e6d72ad7c2cc66310724a57be716 -github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 +github.com/docker/docker 54dddadc7d5d89fe0be88f76979f6f6ab0dede83 github.com/docker/docker-credential-helpers v0.6.1 github.com/docker/go-connections v0.4.0 github.com/docker/go-units v0.3.2 github.com/docker/libtrust aabc10ec26b754e797f9028f4589c5b7bd90dc20 -github.com/fsouza/go-dockerclient 29c1814d12c072344bb91aac5d2ff719db39c523 +github.com/docker/libnetwork 1a06131fb8a047d919f7deaf02a4c414d7884b83 +github.com/fsouza/go-dockerclient v1.3.0 github.com/ghodss/yaml v1.0.0 github.com/gogo/protobuf v1.2.0 github.com/gorilla/context v1.1.1 @@ -38,7 +39,7 @@ github.com/opencontainers/runc v1.0.0-rc6 github.com/opencontainers/runtime-spec v1.0.0 github.com/opencontainers/runtime-tools v0.8.0 github.com/opencontainers/selinux v1.1 -github.com/openshift/imagebuilder 36823496a6868f72bc36282cc475eb8a070c0934 +github.com/openshift/imagebuilder 705fe9255c57f8505efb9723a9ac4082b67973bc github.com/ostreedev/ostree-go 9ab99253d365aac3a330d1f7281cf29f3d22820b github.com/pkg/errors v0.8.1 github.com/pquerna/ffjson d49c2bc1aa135aad0c6f4fc2056623ec78f5d5ac diff --git a/vendor/github.com/containers/psgo/internal/dev/tty.go b/vendor/github.com/containers/psgo/internal/dev/tty.go index cc7d0a422..b7d6f28ac 100644 --- a/vendor/github.com/containers/psgo/internal/dev/tty.go +++ b/vendor/github.com/containers/psgo/internal/dev/tty.go @@ -31,12 +31,9 @@ type TTY struct { Path string } -// cache TTYs to avoid redundant lookups -var devices *[]TTY - // FindTTY return the corresponding TTY to the ttyNr or nil of non could be // found. -func FindTTY(ttyNr uint64) (*TTY, error) { +func FindTTY(ttyNr uint64, devices *[]TTY) (*TTY, error) { // (man 5 proc) The minor device number is contained in the combination // of bits 31 to 20 and 7 to 0; the major device number is in bits 15 // to 8. @@ -44,7 +41,7 @@ func FindTTY(ttyNr uint64) (*TTY, error) { min := (ttyNr & 0xFF) | ((ttyNr >> 20) & 0xFFF) if devices == nil { - devs, err := getTTYs() + devs, err := TTYs() if err != nil { return nil, err } @@ -70,8 +67,8 @@ func minDevNum(rdev uint64) uint64 { return (rdev & 0xff) | ((rdev >> 12) & 0xfff00) } -// getTTYs parses /dev for tty and pts devices. -func getTTYs() (*[]TTY, error) { +// TTYs parses /dev for tty and pts devices. +func TTYs() (*[]TTY, error) { devDir, err := os.Open("/dev/") if err != nil { return nil, err diff --git a/vendor/github.com/containers/psgo/internal/proc/status.go b/vendor/github.com/containers/psgo/internal/proc/status.go index 68e2acff6..29d059361 100644 --- a/vendor/github.com/containers/psgo/internal/proc/status.go +++ b/vendor/github.com/containers/psgo/internal/proc/status.go @@ -21,7 +21,6 @@ import ( "os/exec" "strings" - "github.com/containers/psgo/internal/types" "github.com/pkg/errors" ) @@ -207,11 +206,11 @@ func readStatusDefault(pid string) ([]string, error) { } // ParseStatus parses the /proc/$pid/status file and returns a *Status. -func ParseStatus(ctx *types.PsContext, pid string) (*Status, error) { +func ParseStatus(pid string, joinUserNS bool) (*Status, error) { var lines []string var err error - if ctx.JoinUserNS { + if joinUserNS { lines, err = readStatusUserNS(pid) } else { lines, err = readStatusDefault(pid) diff --git a/vendor/github.com/containers/psgo/internal/process/process.go b/vendor/github.com/containers/psgo/internal/process/process.go index 0afbf721d..2aebfe9cc 100644 --- a/vendor/github.com/containers/psgo/internal/process/process.go +++ b/vendor/github.com/containers/psgo/internal/process/process.go @@ -21,7 +21,6 @@ import ( "github.com/containers/psgo/internal/host" "github.com/containers/psgo/internal/proc" - "github.com/containers/psgo/internal/types" "github.com/opencontainers/runc/libcontainer/user" "github.com/pkg/errors" ) @@ -76,13 +75,13 @@ func LookupUID(uid string) (string, error) { // New returns a new Process with the specified pid and parses the relevant // data from /proc and /dev. -func New(ctx *types.PsContext, pid string) (*Process, error) { +func New(pid string, joinUserNS bool) (*Process, error) { p := Process{Pid: pid} if err := p.parseStat(); err != nil { return nil, err } - if err := p.parseStatus(ctx); err != nil { + if err := p.parseStatus(joinUserNS); err != nil { return nil, err } if err := p.parseCmdLine(); err != nil { @@ -103,10 +102,10 @@ func New(ctx *types.PsContext, pid string) (*Process, error) { } // FromPIDs creates a new Process for each pid. -func FromPIDs(ctx *types.PsContext, pids []string) ([]*Process, error) { +func FromPIDs(pids []string, joinUserNS bool) ([]*Process, error) { processes := []*Process{} for _, pid := range pids { - p, err := New(ctx, pid) + p, err := New(pid, joinUserNS) if err != nil { if os.IsNotExist(errors.Cause(err)) { // proc parsing is racy @@ -131,8 +130,8 @@ func (p *Process) parseStat() error { } // parseStatus parses /proc/$pid/status. -func (p *Process) parseStatus(ctx *types.PsContext) error { - s, err := proc.ParseStatus(ctx, p.Pid) +func (p *Process) parseStatus(joinUserNS bool) error { + s, err := proc.ParseStatus(p.Pid, joinUserNS) if err != nil { return err } diff --git a/vendor/github.com/containers/psgo/internal/types/types.go b/vendor/github.com/containers/psgo/internal/types/types.go deleted file mode 100644 index 1f6fe0eaa..000000000 --- a/vendor/github.com/containers/psgo/internal/types/types.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 psgo authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -// PsContext controls some internals of the psgo library. -type PsContext struct { - // JoinUserNS will force /proc and /dev parsing from within each PIDs - // user namespace. - JoinUserNS bool -} diff --git a/vendor/github.com/containers/psgo/psgo.go b/vendor/github.com/containers/psgo/psgo.go index 47a50264f..e0f102735 100644 --- a/vendor/github.com/containers/psgo/psgo.go +++ b/vendor/github.com/containers/psgo/psgo.go @@ -39,14 +39,22 @@ import ( "github.com/containers/psgo/internal/dev" "github.com/containers/psgo/internal/proc" "github.com/containers/psgo/internal/process" - "github.com/containers/psgo/internal/types" "github.com/pkg/errors" "golang.org/x/sys/unix" ) +type psContext struct { + // Processes in the container. + containersProcesses []*process.Process + // Processes on the host. Used to map those to the ones running in the container. + hostProcesses []*process.Process + // tty and pty devices. + ttys *[]dev.TTY +} + // processFunc is used to map a given aixFormatDescriptor to a corresponding // function extracting the desired data from a process. -type processFunc func(*process.Process) (string, error) +type processFunc func(*process.Process, *psContext) (string, error) // aixFormatDescriptor as mentioned in the ps(1) manpage. A given descriptor // can either be specified via its code (e.g., "%C") or its normal representation @@ -99,13 +107,6 @@ var ( // ErrUnkownDescriptor is returned when an unknown descriptor is parsed. ErrUnkownDescriptor = errors.New("unknown descriptor") - // hostProcesses are the processes on the host. It should only be used - // in the context of containers and is meant to display data of the - // container processes from the host's (i.e., calling process) view. - // Currently, all host processes contain only the required data from - // /proc/$pid/status. - hostProcesses []*process.Process - aixFormatDescriptors = []aixFormatDescriptor{ { code: "%C", @@ -282,11 +283,16 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, return nil, err } + ctx := new(psContext) + // extract data from host processes only on-demand / when at least one // of the specified descriptors requires host data for _, d := range aixDescriptors { if d.onHost { - setHostProcesses(pid) + ctx.hostProcesses, err = hostProcesses(pid) + if err != nil { + return nil, err + } break } } @@ -330,18 +336,17 @@ func JoinNamespaceAndProcessInfo(pid string, descriptors []string) ([][]string, return } - ctx := types.PsContext{ - // join the user NS if the pid's user NS is different - // to the caller's user NS. - JoinUserNS: currentUserNs != pidUserNs, - } - processes, err := process.FromPIDs(&ctx, pids) + // join the user NS if the pid's user NS is different + // to the caller's user NS. + joinUserNS := currentUserNs != pidUserNs + + ctx.containersProcesses, err = process.FromPIDs(pids, joinUserNS) if err != nil { dataErr = err return } - data, dataErr = processDescriptors(aixDescriptors, processes) + data, dataErr = processDescriptors(aixDescriptors, ctx) }() wg.Wait() @@ -417,43 +422,41 @@ func ProcessInfoByPids(pids []string, descriptors []string) ([][]string, error) return nil, err } - ctx := types.PsContext{JoinUserNS: false} - processes, err := process.FromPIDs(&ctx, pids) + ctx := new(psContext) + ctx.containersProcesses, err = process.FromPIDs(pids, false) if err != nil { return nil, err } - return processDescriptors(aixDescriptors, processes) + return processDescriptors(aixDescriptors, ctx) } -// setHostProcesses sets `hostProcesses`. -func setHostProcesses(pid string) error { +// hostProcesses returns all processes running in the current namespace. +func hostProcesses(pid string) ([]*process.Process, error) { // get processes pids, err := proc.GetPIDsFromCgroup(pid) if err != nil { - return err + return nil, err } - ctx := types.PsContext{JoinUserNS: false} - processes, err := process.FromPIDs(&ctx, pids) + processes, err := process.FromPIDs(pids, false) if err != nil { - return err + return nil, err } // set the additional host data for _, p := range processes { if err := p.SetHostData(); err != nil { - return err + return nil, err } } - hostProcesses = processes - return nil + return processes, nil } // processDescriptors calls each `procFn` of all formatDescriptors on each // process and returns an array of tab-separated strings. -func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*process.Process) ([][]string, error) { +func processDescriptors(formatDescriptors []aixFormatDescriptor, ctx *psContext) ([][]string, error) { data := [][]string{} // create header header := []string{} @@ -463,10 +466,10 @@ func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*pr data = append(data, header) // dispatch all descriptor functions on each process - for _, proc := range processes { + for _, proc := range ctx.containersProcesses { pData := []string{} for _, desc := range formatDescriptors { - dataStr, err := desc.procFn(proc) + dataStr, err := desc.procFn(proc, ctx) if err != nil { return nil, err } @@ -480,8 +483,8 @@ func processDescriptors(formatDescriptors []aixFormatDescriptor, processes []*pr // findHostProcess returns the corresponding process from `hostProcesses` or // nil if non is found. -func findHostProcess(p *process.Process) *process.Process { - for _, hp := range hostProcesses { +func findHostProcess(p *process.Process, ctx *psContext) *process.Process { + for _, hp := range ctx.hostProcesses { // We expect the host process to be in another namespace, so // /proc/$pid/status.NSpid must have at least two entries. if len(hp.Status.NSpid) < 2 { @@ -499,78 +502,78 @@ func findHostProcess(p *process.Process) *process.Process { // processGROUP returns the effective group ID of the process. This will be // the textual group ID, if it can be optained, or a decimal representation // otherwise. -func processGROUP(p *process.Process) (string, error) { +func processGROUP(p *process.Process, ctx *psContext) (string, error) { return process.LookupGID(p.Status.Gids[1]) } // processRGROUP returns the real group ID of the process. This will be // the textual group ID, if it can be optained, or a decimal representation // otherwise. -func processRGROUP(p *process.Process) (string, error) { +func processRGROUP(p *process.Process, ctx *psContext) (string, error) { return process.LookupGID(p.Status.Gids[0]) } // processPPID returns the parent process ID of process p. -func processPPID(p *process.Process) (string, error) { +func processPPID(p *process.Process, ctx *psContext) (string, error) { return p.Status.PPid, nil } // processUSER returns the effective user name of the process. This will be // the textual user ID, if it can be optained, or a decimal representation // otherwise. -func processUSER(p *process.Process) (string, error) { +func processUSER(p *process.Process, ctx *psContext) (string, error) { return process.LookupUID(p.Status.Uids[1]) } // processRUSER returns the effective user name of the process. This will be // the textual user ID, if it can be optained, or a decimal representation // otherwise. -func processRUSER(p *process.Process) (string, error) { +func processRUSER(p *process.Process, ctx *psContext) (string, error) { return process.LookupUID(p.Status.Uids[0]) } // processName returns the name of process p in the format "[$name]". -func processName(p *process.Process) (string, error) { +func processName(p *process.Process, ctx *psContext) (string, error) { return fmt.Sprintf("[%s]", p.Status.Name), nil } // processARGS returns the command of p with all its arguments. -func processARGS(p *process.Process) (string, error) { +func processARGS(p *process.Process, ctx *psContext) (string, error) { // ps (1) returns "[$name]" if command/args are empty if p.CmdLine[0] == "" { - return processName(p) + return processName(p, ctx) } return strings.Join(p.CmdLine, " "), nil } // processCOMM returns the command name (i.e., executable name) of process p. -func processCOMM(p *process.Process) (string, error) { +func processCOMM(p *process.Process, ctx *psContext) (string, error) { // ps (1) returns "[$name]" if command/args are empty if p.CmdLine[0] == "" { - return processName(p) + return processName(p, ctx) } spl := strings.Split(p.CmdLine[0], "/") return spl[len(spl)-1], nil } // processNICE returns the nice value of process p. -func processNICE(p *process.Process) (string, error) { +func processNICE(p *process.Process, ctx *psContext) (string, error) { return p.Stat.Nice, nil } // processPID returns the process ID of process p. -func processPID(p *process.Process) (string, error) { +func processPID(p *process.Process, ctx *psContext) (string, error) { return p.Pid, nil } // processPGID returns the process group ID of process p. -func processPGID(p *process.Process) (string, error) { +func processPGID(p *process.Process, ctx *psContext) (string, error) { return p.Stat.Pgrp, nil } // processPCPU returns how many percent of the CPU time process p uses as // a three digit float as string. -func processPCPU(p *process.Process) (string, error) { +func processPCPU(p *process.Process, ctx *psContext) (string, error) { elapsed, err := p.ElapsedTime() if err != nil { return "", err @@ -585,7 +588,7 @@ func processPCPU(p *process.Process) (string, error) { } // processETIME returns the elapsed time since the process was started. -func processETIME(p *process.Process) (string, error) { +func processETIME(p *process.Process, ctx *psContext) (string, error) { elapsed, err := p.ElapsedTime() if err != nil { return "", nil @@ -594,7 +597,7 @@ func processETIME(p *process.Process) (string, error) { } // processTIME returns the cumulative CPU time of process p. -func processTIME(p *process.Process) (string, error) { +func processTIME(p *process.Process, ctx *psContext) (string, error) { cpu, err := p.CPUTime() if err != nil { return "", err @@ -603,13 +606,13 @@ func processTIME(p *process.Process) (string, error) { } // processTTY returns the controlling tty (terminal) of process p. -func processTTY(p *process.Process) (string, error) { +func processTTY(p *process.Process, ctx *psContext) (string, error) { ttyNr, err := strconv.ParseUint(p.Stat.TtyNr, 10, 64) if err != nil { return "", nil } - tty, err := dev.FindTTY(ttyNr) + tty, err := dev.FindTTY(ttyNr, ctx.ttys) if err != nil { return "", nil } @@ -623,7 +626,7 @@ func processTTY(p *process.Process) (string, error) { // processVSZ returns the virtual memory size of process p in KiB (1024-byte // units). -func processVSZ(p *process.Process) (string, error) { +func processVSZ(p *process.Process, ctx *psContext) (string, error) { vmsize, err := strconv.Atoi(p.Stat.Vsize) if err != nil { return "", err @@ -653,41 +656,41 @@ func parseCAP(cap string) (string, error) { // processCAPAMB returns the set of ambient capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPAMB(p *process.Process) (string, error) { +func processCAPAMB(p *process.Process, ctx *psContext) (string, error) { return parseCAP(p.Status.CapAmb) } // processCAPINH returns the set of inheritable capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPINH(p *process.Process) (string, error) { +func processCAPINH(p *process.Process, ctx *psContext) (string, error) { return parseCAP(p.Status.CapInh) } // processCAPPRM returns the set of permitted capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPPRM(p *process.Process) (string, error) { +func processCAPPRM(p *process.Process, ctx *psContext) (string, error) { return parseCAP(p.Status.CapPrm) } // processCAPEFF returns the set of effective capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPEFF(p *process.Process) (string, error) { +func processCAPEFF(p *process.Process, ctx *psContext) (string, error) { return parseCAP(p.Status.CapEff) } // processCAPBND returns the set of bounding capabilties associated with // process p. If all capabilties are set, "full" is returned. If no // capability is enabled, "none" is returned. -func processCAPBND(p *process.Process) (string, error) { +func processCAPBND(p *process.Process, ctx *psContext) (string, error) { return parseCAP(p.Status.CapBnd) } // processSECCOMP returns the seccomp mode of the process (i.e., disabled, // strict or filter) or "?" if /proc/$pid/status.seccomp has a unknown value. -func processSECCOMP(p *process.Process) (string, error) { +func processSECCOMP(p *process.Process, ctx *psContext) (string, error) { switch p.Status.Seccomp { case "0": return "disabled", nil @@ -702,14 +705,14 @@ func processSECCOMP(p *process.Process) (string, error) { // processLABEL returns the process label of process p or "?" if the system // doesn't support labeling. -func processLABEL(p *process.Process) (string, error) { +func processLABEL(p *process.Process, ctx *psContext) (string, error) { return p.Label, nil } // processHPID returns the PID of the corresponding host process of the // (container) or "?" if no corresponding process could be found. -func processHPID(p *process.Process) (string, error) { - if hp := findHostProcess(p); hp != nil { +func processHPID(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { return hp.Pid, nil } return "?", nil @@ -717,8 +720,8 @@ func processHPID(p *process.Process) (string, error) { // processHUSER returns the effective user ID of the corresponding host process // of the (container) or "?" if no corresponding process could be found. -func processHUSER(p *process.Process) (string, error) { - if hp := findHostProcess(p); hp != nil { +func processHUSER(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { return hp.Huser, nil } return "?", nil @@ -727,14 +730,14 @@ func processHUSER(p *process.Process) (string, error) { // processHGROUP returns the effective group ID of the corresponding host // process of the (container) or "?" if no corresponding process could be // found. -func processHGROUP(p *process.Process) (string, error) { - if hp := findHostProcess(p); hp != nil { +func processHGROUP(p *process.Process, ctx *psContext) (string, error) { + if hp := findHostProcess(p, ctx); hp != nil { return hp.Hgroup, nil } return "?", nil } // processState returns the process state of process p. -func processState(p *process.Process) (string, error) { +func processState(p *process.Process, ctx *psContext) (string, error) { return p.Status.State, nil } diff --git a/vendor/github.com/docker/docker/LICENSE b/vendor/github.com/docker/docker/LICENSE index 9c8e20ab8..6d8d58fb6 100644 --- a/vendor/github.com/docker/docker/LICENSE +++ b/vendor/github.com/docker/docker/LICENSE @@ -176,7 +176,7 @@ END OF TERMS AND CONDITIONS - Copyright 2013-2017 Docker, Inc. + Copyright 2013-2018 Docker, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/github.com/docker/docker/api/common.go b/vendor/github.com/docker/docker/api/common.go index d0229e038..aa146cdae 100644 --- a/vendor/github.com/docker/docker/api/common.go +++ b/vendor/github.com/docker/docker/api/common.go @@ -1,11 +1,11 @@ -package api +package api // import "github.com/docker/docker/api" // Common constants for daemon and client. const ( // DefaultVersion of Current REST API - DefaultVersion string = "1.34" + DefaultVersion = "1.40" // NoBaseImageSpecifier is the symbol used by the FROM // command to specify that no base image is to be used. - NoBaseImageSpecifier string = "scratch" + NoBaseImageSpecifier = "scratch" ) diff --git a/vendor/github.com/docker/docker/api/common_unix.go b/vendor/github.com/docker/docker/api/common_unix.go index 081e61c45..504b0c90d 100644 --- a/vendor/github.com/docker/docker/api/common_unix.go +++ b/vendor/github.com/docker/docker/api/common_unix.go @@ -1,6 +1,6 @@ // +build !windows -package api +package api // import "github.com/docker/docker/api" // MinVersion represents Minimum REST API version supported -const MinVersion string = "1.12" +const MinVersion = "1.12" diff --git a/vendor/github.com/docker/docker/api/common_windows.go b/vendor/github.com/docker/docker/api/common_windows.go index a6268a4ff..590ba5479 100644 --- a/vendor/github.com/docker/docker/api/common_windows.go +++ b/vendor/github.com/docker/docker/api/common_windows.go @@ -1,4 +1,4 @@ -package api +package api // import "github.com/docker/docker/api" // MinVersion represents Minimum REST API version supported // Technically the first daemon API version released on Windows is v1.25 in diff --git a/vendor/github.com/docker/docker/api/types/auth.go b/vendor/github.com/docker/docker/api/types/auth.go index 056af6b84..ddf15bb18 100644 --- a/vendor/github.com/docker/docker/api/types/auth.go +++ b/vendor/github.com/docker/docker/api/types/auth.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" // AuthConfig contains authorization information for connecting to a Registry type AuthConfig struct { diff --git a/vendor/github.com/docker/docker/api/types/blkiodev/blkio.go b/vendor/github.com/docker/docker/api/types/blkiodev/blkio.go index 931ae10ab..bf3463b90 100644 --- a/vendor/github.com/docker/docker/api/types/blkiodev/blkio.go +++ b/vendor/github.com/docker/docker/api/types/blkiodev/blkio.go @@ -1,4 +1,4 @@ -package blkiodev +package blkiodev // import "github.com/docker/docker/api/types/blkiodev" import "fmt" diff --git a/vendor/github.com/docker/docker/api/types/client.go b/vendor/github.com/docker/docker/api/types/client.go index db37f1fe4..3b698c2c2 100644 --- a/vendor/github.com/docker/docker/api/types/client.go +++ b/vendor/github.com/docker/docker/api/types/client.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" import ( "bufio" @@ -74,6 +74,7 @@ type ContainerLogsOptions struct { ShowStdout bool ShowStderr bool Since string + Until string Timestamps bool Follow bool Tail string @@ -180,8 +181,24 @@ type ImageBuildOptions struct { Target string SessionID string Platform string + // Version specifies the version of the unerlying builder to use + Version BuilderVersion + // BuildID is an optional identifier that can be passed together with the + // build request. The same identifier can be used to gracefully cancel the + // build with the cancel request. + BuildID string } +// BuilderVersion sets the version of underlying builder to use +type BuilderVersion string + +const ( + // BuilderV1 is the first generation builder in docker daemon + BuilderV1 BuilderVersion = "1" + // BuilderBuildKit is builder based on moby/buildkit project + BuilderBuildKit = "2" +) + // ImageBuildResponse holds information // returned by a server after building // an image. diff --git a/vendor/github.com/docker/docker/api/types/configs.go b/vendor/github.com/docker/docker/api/types/configs.go index 20c19f213..178e911a7 100644 --- a/vendor/github.com/docker/docker/api/types/configs.go +++ b/vendor/github.com/docker/docker/api/types/configs.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" import ( "github.com/docker/docker/api/types/container" @@ -25,19 +25,6 @@ type ContainerRmConfig struct { ForceRemove, RemoveVolume, RemoveLink bool } -// ContainerCommitConfig contains build configs for commit operation, -// and is used when making a commit with the current state of the container. -type ContainerCommitConfig struct { - Pause bool - Repo string - Tag string - Author string - Comment string - // merge container config into commit config before commit - MergeConfigs bool - Config *container.Config -} - // ExecConfig is a small subset of the Config struct that holds the configuration // for the exec feature of docker. type ExecConfig struct { @@ -50,6 +37,7 @@ type ExecConfig struct { Detach bool // Execute in detach mode DetachKeys string // Escape keys for detach Env []string // Environment variables + WorkingDir string // Working directory Cmd []string // Execution commands and args } @@ -67,3 +55,10 @@ type PluginEnableConfig struct { type PluginDisableConfig struct { ForceDisable bool } + +// NetworkListConfig stores the options available for listing networks +type NetworkListConfig struct { + // TODO(@cpuguy83): naming is hard, this is pulled from what was being used in the router before moving here + Detailed bool + Verbose bool +} diff --git a/vendor/github.com/docker/docker/api/types/container/config.go b/vendor/github.com/docker/docker/api/types/container/config.go index 55a03fc98..89ad08c23 100644 --- a/vendor/github.com/docker/docker/api/types/container/config.go +++ b/vendor/github.com/docker/docker/api/types/container/config.go @@ -1,4 +1,4 @@ -package container +package container // import "github.com/docker/docker/api/types/container" import ( "time" diff --git a/vendor/github.com/docker/docker/api/types/container/container_changes.go b/vendor/github.com/docker/docker/api/types/container/container_changes.go index 767945a53..c909d6ca3 100644 --- a/vendor/github.com/docker/docker/api/types/container/container_changes.go +++ b/vendor/github.com/docker/docker/api/types/container/container_changes.go @@ -7,7 +7,7 @@ package container // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// ContainerChangeResponseItem container change response item +// ContainerChangeResponseItem change item in response to ContainerChanges operation // swagger:model ContainerChangeResponseItem type ContainerChangeResponseItem struct { diff --git a/vendor/github.com/docker/docker/api/types/container/container_create.go b/vendor/github.com/docker/docker/api/types/container/container_create.go index c95023b81..49efa0f2c 100644 --- a/vendor/github.com/docker/docker/api/types/container/container_create.go +++ b/vendor/github.com/docker/docker/api/types/container/container_create.go @@ -7,7 +7,7 @@ package container // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// ContainerCreateCreatedBody container create created body +// ContainerCreateCreatedBody OK response to ContainerCreate operation // swagger:model ContainerCreateCreatedBody type ContainerCreateCreatedBody struct { diff --git a/vendor/github.com/docker/docker/api/types/container/container_top.go b/vendor/github.com/docker/docker/api/types/container/container_top.go index 78bc37ee5..ba41edcf3 100644 --- a/vendor/github.com/docker/docker/api/types/container/container_top.go +++ b/vendor/github.com/docker/docker/api/types/container/container_top.go @@ -7,7 +7,7 @@ package container // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// ContainerTopOKBody container top o k body +// ContainerTopOKBody OK response to ContainerTop operation // swagger:model ContainerTopOKBody type ContainerTopOKBody struct { diff --git a/vendor/github.com/docker/docker/api/types/container/container_update.go b/vendor/github.com/docker/docker/api/types/container/container_update.go index 2339366fb..7630ae54c 100644 --- a/vendor/github.com/docker/docker/api/types/container/container_update.go +++ b/vendor/github.com/docker/docker/api/types/container/container_update.go @@ -7,7 +7,7 @@ package container // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// ContainerUpdateOKBody container update o k body +// ContainerUpdateOKBody OK response to ContainerUpdate operation // swagger:model ContainerUpdateOKBody type ContainerUpdateOKBody struct { diff --git a/vendor/github.com/docker/docker/api/types/container/container_wait.go b/vendor/github.com/docker/docker/api/types/container/container_wait.go index 77ecdbaf7..9e3910a6b 100644 --- a/vendor/github.com/docker/docker/api/types/container/container_wait.go +++ b/vendor/github.com/docker/docker/api/types/container/container_wait.go @@ -7,10 +7,22 @@ package container // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// ContainerWaitOKBody container wait o k body +// ContainerWaitOKBodyError container waiting error, if any +// swagger:model ContainerWaitOKBodyError +type ContainerWaitOKBodyError struct { + + // Details of an error + Message string `json:"Message,omitempty"` +} + +// ContainerWaitOKBody OK response to ContainerWait operation // swagger:model ContainerWaitOKBody type ContainerWaitOKBody struct { + // error + // Required: true + Error *ContainerWaitOKBodyError `json:"Error"` + // Exit code of the container // Required: true StatusCode int64 `json:"StatusCode"` diff --git a/vendor/github.com/docker/docker/api/types/container/host_config.go b/vendor/github.com/docker/docker/api/types/container/host_config.go index bb421b388..f4f5c09f8 100644 --- a/vendor/github.com/docker/docker/api/types/container/host_config.go +++ b/vendor/github.com/docker/docker/api/types/container/host_config.go @@ -1,4 +1,4 @@ -package container +package container // import "github.com/docker/docker/api/types/container" import ( "strings" @@ -20,6 +20,27 @@ func (i Isolation) IsDefault() bool { return strings.ToLower(string(i)) == "default" || string(i) == "" } +// IsHyperV indicates the use of a Hyper-V partition for isolation +func (i Isolation) IsHyperV() bool { + return strings.ToLower(string(i)) == "hyperv" +} + +// IsProcess indicates the use of process isolation +func (i Isolation) IsProcess() bool { + return strings.ToLower(string(i)) == "process" +} + +const ( + // IsolationEmpty is unspecified (same behavior as default) + IsolationEmpty = Isolation("") + // IsolationDefault is the default isolation mode on current daemon + IsolationDefault = Isolation("default") + // IsolationProcess is process isolation mode + IsolationProcess = Isolation("process") + // IsolationHyperV is HyperV isolation mode + IsolationHyperV = Isolation("hyperv") +) + // IpcMode represents the container ipc stack. type IpcMode string @@ -308,11 +329,12 @@ type Resources struct { DeviceCgroupRules []string // List of rule to be added to the device cgroup DiskQuota int64 // Disk limit (in bytes) KernelMemory int64 // Kernel memory limit (in bytes) + KernelMemoryTCP int64 // Hard limit for kernel TCP buffer memory (in bytes) MemoryReservation int64 // Memory soft limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwappiness *int64 // Tuning container memory swappiness behaviour OomKillDisable *bool // Whether to disable OOM Killer or not - PidsLimit int64 // Setting pids limit for a container + PidsLimit *int64 // Setting PIDs limit for a container; Set `0` or `-1` for unlimited, or `null` to not change. Ulimits []*units.Ulimit // List of ulimits to be set in the container // Applicable to Windows @@ -348,9 +370,10 @@ type HostConfig struct { // Applicable to UNIX platforms CapAdd strslice.StrSlice // List of kernel capabilities to add to the container CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container - DNS []string `json:"Dns"` // List of DNS server to lookup - DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for - DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for + Capabilities []string `json:"Capabilities"` // List of kernel capabilities to be available for container (this overrides the default set) + DNS []string `json:"Dns"` // List of DNS server to lookup + DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for + DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts GroupAdd []string // List of additional groups that the container process will run as IpcMode IpcMode // IPC namespace to use for the container @@ -380,6 +403,12 @@ type HostConfig struct { // Mounts specs used by the container Mounts []mount.Mount `json:",omitempty"` + // MaskedPaths is the list of paths to be masked inside the container (this overrides the default set of paths) + MaskedPaths []string + + // ReadonlyPaths is the list of paths to be set as read-only inside the container (this overrides the default set of paths) + ReadonlyPaths []string + // Run a custom init inside the container, if null, use the daemon's configured settings Init *bool `json:",omitempty"` } diff --git a/vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go b/vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go index 2d664d1c9..cf6fdf440 100644 --- a/vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go +++ b/vendor/github.com/docker/docker/api/types/container/hostconfig_unix.go @@ -1,6 +1,6 @@ // +build !windows -package container +package container // import "github.com/docker/docker/api/types/container" // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { diff --git a/vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go b/vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go index 469923f7e..99f803a5b 100644 --- a/vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go +++ b/vendor/github.com/docker/docker/api/types/container/hostconfig_windows.go @@ -1,8 +1,4 @@ -package container - -import ( - "strings" -) +package container // import "github.com/docker/docker/api/types/container" // IsBridge indicates whether container uses the bridge network stack // in windows it is given the name NAT @@ -21,16 +17,6 @@ func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsNone() && !n.IsBridge() && !n.IsContainer() } -// IsHyperV indicates the use of a Hyper-V partition for isolation -func (i Isolation) IsHyperV() bool { - return strings.ToLower(string(i)) == "hyperv" -} - -// IsProcess indicates the use of process isolation -func (i Isolation) IsProcess() bool { - return strings.ToLower(string(i)) == "process" -} - // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { return i.IsDefault() || i.IsHyperV() || i.IsProcess() diff --git a/vendor/github.com/docker/docker/api/types/container/waitcondition.go b/vendor/github.com/docker/docker/api/types/container/waitcondition.go index 64820fe35..cd8311f99 100644 --- a/vendor/github.com/docker/docker/api/types/container/waitcondition.go +++ b/vendor/github.com/docker/docker/api/types/container/waitcondition.go @@ -1,4 +1,4 @@ -package container +package container // import "github.com/docker/docker/api/types/container" // WaitCondition is a type used to specify a container state for which // to wait. diff --git a/vendor/github.com/docker/docker/api/types/events/events.go b/vendor/github.com/docker/docker/api/types/events/events.go index e292565b6..027c6edb7 100644 --- a/vendor/github.com/docker/docker/api/types/events/events.go +++ b/vendor/github.com/docker/docker/api/types/events/events.go @@ -1,4 +1,4 @@ -package events +package events // import "github.com/docker/docker/api/types/events" const ( // ContainerEventType is the event type that containers generate diff --git a/vendor/github.com/docker/docker/api/types/filters/parse.go b/vendor/github.com/docker/docker/api/types/filters/parse.go index d45d0528f..d8f19ae22 100644 --- a/vendor/github.com/docker/docker/api/types/filters/parse.go +++ b/vendor/github.com/docker/docker/api/types/filters/parse.go @@ -1,7 +1,7 @@ /*Package filters provides tools for encoding a mapping of keys to a set of multiple values. */ -package filters +package filters // import "github.com/docker/docker/api/types/filters" import ( "encoding/json" @@ -323,6 +323,22 @@ func (args Args) WalkValues(field string, op func(value string) error) error { return nil } +// Clone returns a copy of args. +func (args Args) Clone() (newArgs Args) { + newArgs.fields = make(map[string]map[string]bool, len(args.fields)) + for k, m := range args.fields { + var mm map[string]bool + if m != nil { + mm = make(map[string]bool, len(m)) + for kk, v := range m { + mm[kk] = v + } + } + newArgs.fields[k] = mm + } + return newArgs +} + func deprecatedArgs(d map[string][]string) map[string]map[string]bool { m := map[string]map[string]bool{} for k, v := range d { diff --git a/vendor/github.com/docker/docker/api/types/image/image_history.go b/vendor/github.com/docker/docker/api/types/image/image_history.go index 0dd30c729..d6b354bcd 100644 --- a/vendor/github.com/docker/docker/api/types/image/image_history.go +++ b/vendor/github.com/docker/docker/api/types/image/image_history.go @@ -7,7 +7,7 @@ package image // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// HistoryResponseItem history response item +// HistoryResponseItem individual image layer information in response to ImageHistory operation // swagger:model HistoryResponseItem type HistoryResponseItem struct { diff --git a/vendor/github.com/docker/docker/api/types/mount/mount.go b/vendor/github.com/docker/docker/api/types/mount/mount.go index b7d133cd8..ab4446b38 100644 --- a/vendor/github.com/docker/docker/api/types/mount/mount.go +++ b/vendor/github.com/docker/docker/api/types/mount/mount.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/api/types/mount" import ( "os" @@ -79,7 +79,8 @@ const ( // BindOptions defines options specific to mounts of type "bind". type BindOptions struct { - Propagation Propagation `json:",omitempty"` + Propagation Propagation `json:",omitempty"` + NonRecursive bool `json:",omitempty"` } // VolumeOptions represents the options for a mount of type volume. diff --git a/vendor/github.com/docker/docker/api/types/network/network.go b/vendor/github.com/docker/docker/api/types/network/network.go index 7c7dbacc8..71e97338f 100644 --- a/vendor/github.com/docker/docker/api/types/network/network.go +++ b/vendor/github.com/docker/docker/api/types/network/network.go @@ -1,4 +1,8 @@ -package network +package network // import "github.com/docker/docker/api/types/network" +import ( + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/errdefs" +) // Address represents an IP address type Address struct { @@ -106,3 +110,18 @@ type NetworkingConfig struct { type ConfigReference struct { Network string } + +var acceptedFilters = map[string]bool{ + "dangling": true, + "driver": true, + "id": true, + "label": true, + "name": true, + "scope": true, + "type": true, +} + +// ValidateFilters validates the list of filter args with the available filters. +func ValidateFilters(filter filters.Args) error { + return errdefs.InvalidParameter(filter.Validate(acceptedFilters)) +} diff --git a/vendor/github.com/docker/docker/api/types/plugin.go b/vendor/github.com/docker/docker/api/types/plugin.go index cab333e01..abae48b9a 100644 --- a/vendor/github.com/docker/docker/api/types/plugin.go +++ b/vendor/github.com/docker/docker/api/types/plugin.go @@ -121,6 +121,9 @@ type PluginConfigArgs struct { // swagger:model PluginConfigInterface type PluginConfigInterface struct { + // Protocol to use for clients connecting to the plugin. + ProtocolScheme string `json:"ProtocolScheme,omitempty"` + // socket // Required: true Socket string `json:"Socket"` diff --git a/vendor/github.com/docker/docker/api/types/plugin_responses.go b/vendor/github.com/docker/docker/api/types/plugin_responses.go index 18f743fcd..60d1fb5ad 100644 --- a/vendor/github.com/docker/docker/api/types/plugin_responses.go +++ b/vendor/github.com/docker/docker/api/types/plugin_responses.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" import ( "encoding/json" diff --git a/vendor/github.com/docker/docker/api/types/port.go b/vendor/github.com/docker/docker/api/types/port.go index ad52d46d5..d91234744 100644 --- a/vendor/github.com/docker/docker/api/types/port.go +++ b/vendor/github.com/docker/docker/api/types/port.go @@ -7,7 +7,7 @@ package types // swagger:model Port type Port struct { - // IP + // Host IP address that the container's port is mapped to IP string `json:"IP,omitempty"` // Port on the container diff --git a/vendor/github.com/docker/docker/api/types/registry/authenticate.go b/vendor/github.com/docker/docker/api/types/registry/authenticate.go index 42cac4430..f0a2113e4 100644 --- a/vendor/github.com/docker/docker/api/types/registry/authenticate.go +++ b/vendor/github.com/docker/docker/api/types/registry/authenticate.go @@ -1,4 +1,4 @@ -package registry +package registry // import "github.com/docker/docker/api/types/registry" // ---------------------------------------------------------------------------- // DO NOT EDIT THIS FILE diff --git a/vendor/github.com/docker/docker/api/types/registry/registry.go b/vendor/github.com/docker/docker/api/types/registry/registry.go index b98a943a1..8789ad3b3 100644 --- a/vendor/github.com/docker/docker/api/types/registry/registry.go +++ b/vendor/github.com/docker/docker/api/types/registry/registry.go @@ -1,4 +1,4 @@ -package registry +package registry // import "github.com/docker/docker/api/types/registry" import ( "encoding/json" diff --git a/vendor/github.com/docker/docker/api/types/seccomp.go b/vendor/github.com/docker/docker/api/types/seccomp.go index 7d62c9a43..2259c6be1 100644 --- a/vendor/github.com/docker/docker/api/types/seccomp.go +++ b/vendor/github.com/docker/docker/api/types/seccomp.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" // Seccomp represents the config for a seccomp profile for syscall restriction. type Seccomp struct { @@ -77,8 +77,9 @@ type Arg struct { // Filter is used to conditionally apply Seccomp rules type Filter struct { - Caps []string `json:"caps,omitempty"` - Arches []string `json:"arches,omitempty"` + Caps []string `json:"caps,omitempty"` + Arches []string `json:"arches,omitempty"` + MinKernel string `json:"minKernel,omitempty"` } // Syscall is used to match a group of syscalls in Seccomp diff --git a/vendor/github.com/docker/docker/api/types/stats.go b/vendor/github.com/docker/docker/api/types/stats.go index 7ca76a5b6..20daebed1 100644 --- a/vendor/github.com/docker/docker/api/types/stats.go +++ b/vendor/github.com/docker/docker/api/types/stats.go @@ -1,6 +1,6 @@ // Package types is used for API stability in the types and response to the // consumers of the API stats endpoint. -package types +package types // import "github.com/docker/docker/api/types" import "time" @@ -120,7 +120,7 @@ type NetworkStats struct { RxBytes uint64 `json:"rx_bytes"` // Packets received. Windows and Linux. RxPackets uint64 `json:"rx_packets"` - // Received errors. Not used on Windows. Note that we dont `omitempty` this + // Received errors. Not used on Windows. Note that we don't `omitempty` this // field as it is expected in the >=v1.21 API stats structure. RxErrors uint64 `json:"rx_errors"` // Incoming packets dropped. Windows and Linux. @@ -129,7 +129,7 @@ type NetworkStats struct { TxBytes uint64 `json:"tx_bytes"` // Packets sent. Windows and Linux. TxPackets uint64 `json:"tx_packets"` - // Sent errors. Not used on Windows. Note that we dont `omitempty` this + // Sent errors. Not used on Windows. Note that we don't `omitempty` this // field as it is expected in the >=v1.21 API stats structure. TxErrors uint64 `json:"tx_errors"` // Outgoing packets dropped. Windows and Linux. diff --git a/vendor/github.com/docker/docker/api/types/strslice/strslice.go b/vendor/github.com/docker/docker/api/types/strslice/strslice.go index bad493fb8..82921cebc 100644 --- a/vendor/github.com/docker/docker/api/types/strslice/strslice.go +++ b/vendor/github.com/docker/docker/api/types/strslice/strslice.go @@ -1,4 +1,4 @@ -package strslice +package strslice // import "github.com/docker/docker/api/types/strslice" import "encoding/json" diff --git a/vendor/github.com/docker/docker/api/types/swarm/common.go b/vendor/github.com/docker/docker/api/types/swarm/common.go index 2834cf202..ef020f458 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/common.go +++ b/vendor/github.com/docker/docker/api/types/swarm/common.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import "time" diff --git a/vendor/github.com/docker/docker/api/types/swarm/config.go b/vendor/github.com/docker/docker/api/types/swarm/config.go index 0fb021ce9..16202ccce 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/config.go +++ b/vendor/github.com/docker/docker/api/types/swarm/config.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import "os" @@ -13,6 +13,10 @@ type Config struct { type ConfigSpec struct { Annotations Data []byte `json:",omitempty"` + + // Templating controls whether and how to evaluate the config payload as + // a template. If it is not set, no templating is used. + Templating *Driver `json:",omitempty"` } // ConfigReferenceFileTarget is a file target in a config reference @@ -23,9 +27,14 @@ type ConfigReferenceFileTarget struct { Mode os.FileMode } +// ConfigReferenceRuntimeTarget is a target for a config specifying that it +// isn't mounted into the container but instead has some other purpose. +type ConfigReferenceRuntimeTarget struct{} + // ConfigReference is a reference to a config in swarm type ConfigReference struct { - File *ConfigReferenceFileTarget + File *ConfigReferenceFileTarget `json:",omitempty"` + Runtime *ConfigReferenceRuntimeTarget `json:",omitempty"` ConfigID string ConfigName string } diff --git a/vendor/github.com/docker/docker/api/types/swarm/container.go b/vendor/github.com/docker/docker/api/types/swarm/container.go index 6f8b45f6b..48190c176 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/container.go +++ b/vendor/github.com/docker/docker/api/types/swarm/container.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import ( "time" @@ -33,6 +33,7 @@ type SELinuxContext struct { // CredentialSpec for managed service account (Windows only) type CredentialSpec struct { + Config string File string Registry string } @@ -55,6 +56,7 @@ type ContainerSpec struct { User string `json:",omitempty"` Groups []string `json:",omitempty"` Privileges *Privileges `json:",omitempty"` + Init *bool `json:",omitempty"` StopSignal string `json:",omitempty"` TTY bool `json:",omitempty"` OpenStdin bool `json:",omitempty"` @@ -65,8 +67,10 @@ type ContainerSpec struct { // The format of extra hosts on swarmkit is specified in: // http://man7.org/linux/man-pages/man5/hosts.5.html // IP_address canonical_hostname [aliases...] - Hosts []string `json:",omitempty"` - DNSConfig *DNSConfig `json:",omitempty"` - Secrets []*SecretReference `json:",omitempty"` - Configs []*ConfigReference `json:",omitempty"` + Hosts []string `json:",omitempty"` + DNSConfig *DNSConfig `json:",omitempty"` + Secrets []*SecretReference `json:",omitempty"` + Configs []*ConfigReference `json:",omitempty"` + Isolation container.Isolation `json:",omitempty"` + Sysctls map[string]string `json:",omitempty"` } diff --git a/vendor/github.com/docker/docker/api/types/swarm/network.go b/vendor/github.com/docker/docker/api/types/swarm/network.go index 97c484e14..98ef3284d 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/network.go +++ b/vendor/github.com/docker/docker/api/types/swarm/network.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import ( "github.com/docker/docker/api/types/network" @@ -62,6 +62,8 @@ const ( PortConfigProtocolTCP PortConfigProtocol = "tcp" // PortConfigProtocolUDP UDP PortConfigProtocolUDP PortConfigProtocol = "udp" + // PortConfigProtocolSCTP SCTP + PortConfigProtocolSCTP PortConfigProtocol = "sctp" ) // EndpointVirtualIP represents the virtual ip of a port. diff --git a/vendor/github.com/docker/docker/api/types/swarm/node.go b/vendor/github.com/docker/docker/api/types/swarm/node.go index 28c6851e9..1e30f5fa1 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/node.go +++ b/vendor/github.com/docker/docker/api/types/swarm/node.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" // Node represents a node. type Node struct { diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime.go b/vendor/github.com/docker/docker/api/types/swarm/runtime.go index c4c731dc8..0c77403cc 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime.go +++ b/vendor/github.com/docker/docker/api/types/swarm/runtime.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" // RuntimeType is the type of runtime used for the TaskSpec type RuntimeType string @@ -11,9 +11,17 @@ const ( RuntimeContainer RuntimeType = "container" // RuntimePlugin is the plugin based runtime RuntimePlugin RuntimeType = "plugin" + // RuntimeNetworkAttachment is the network attachment runtime + RuntimeNetworkAttachment RuntimeType = "attachment" // RuntimeURLContainer is the proto url for the container type RuntimeURLContainer RuntimeURL = "types.docker.com/RuntimeContainer" // RuntimeURLPlugin is the proto url for the plugin type RuntimeURLPlugin RuntimeURL = "types.docker.com/RuntimePlugin" ) + +// NetworkAttachmentSpec represents the runtime spec type for network +// attachment tasks +type NetworkAttachmentSpec struct { + ContainerID string +} diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go b/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go index 47ae234ef..98c2806c3 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go +++ b/vendor/github.com/docker/docker/api/types/swarm/runtime/gen.go @@ -1,3 +1,3 @@ //go:generate protoc -I . --gogofast_out=import_path=github.com/docker/docker/api/types/swarm/runtime:. plugin.proto -package runtime +package runtime // import "github.com/docker/docker/api/types/swarm/runtime" diff --git a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto b/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto index 06eb7ba65..6d63b7783 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto +++ b/vendor/github.com/docker/docker/api/types/swarm/runtime/plugin.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +option go_package = "github.com/docker/docker/api/types/swarm/runtime;runtime"; + // PluginSpec defines the base payload which clients can specify for creating // a service with the plugin runtime. message PluginSpec { diff --git a/vendor/github.com/docker/docker/api/types/swarm/secret.go b/vendor/github.com/docker/docker/api/types/swarm/secret.go index f9b1e9266..d5213ec98 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/secret.go +++ b/vendor/github.com/docker/docker/api/types/swarm/secret.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import "os" @@ -14,6 +14,10 @@ type SecretSpec struct { Annotations Data []byte `json:",omitempty"` Driver *Driver `json:",omitempty"` // name of the secrets driver used to fetch the secret's value from an external secret store + + // Templating controls whether and how to evaluate the secret payload as + // a template. If it is not set, no templating is used. + Templating *Driver `json:",omitempty"` } // SecretReferenceFileTarget is a file target in a secret reference diff --git a/vendor/github.com/docker/docker/api/types/swarm/service.go b/vendor/github.com/docker/docker/api/types/swarm/service.go index fa31a7ec8..abf192e75 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/service.go +++ b/vendor/github.com/docker/docker/api/types/swarm/service.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import "time" diff --git a/vendor/github.com/docker/docker/api/types/swarm/swarm.go b/vendor/github.com/docker/docker/api/types/swarm/swarm.go index b65fa86da..484cd0be7 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/swarm.go +++ b/vendor/github.com/docker/docker/api/types/swarm/swarm.go @@ -1,6 +1,8 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" -import "time" +import ( + "time" +) // ClusterInfo represents info about the cluster for outputting in "info" // it contains the same information as "Swarm", but without the JoinTokens @@ -10,6 +12,9 @@ type ClusterInfo struct { Spec Spec TLSInfo TLSInfo RootRotationInProgress bool + DefaultAddrPool []string + SubnetSize uint32 + DataPathPort uint32 } // Swarm represents a swarm. @@ -149,10 +154,13 @@ type InitRequest struct { ListenAddr string AdvertiseAddr string DataPathAddr string + DataPathPort uint32 ForceNewCluster bool Spec Spec AutoLockManagers bool Availability NodeAvailability + DefaultAddrPool []string + SubnetSize uint32 } // JoinRequest is the request used to join a swarm. diff --git a/vendor/github.com/docker/docker/api/types/swarm/task.go b/vendor/github.com/docker/docker/api/types/swarm/task.go index ff11b07e7..d5a57df5d 100644 --- a/vendor/github.com/docker/docker/api/types/swarm/task.go +++ b/vendor/github.com/docker/docker/api/types/swarm/task.go @@ -1,4 +1,4 @@ -package swarm +package swarm // import "github.com/docker/docker/api/types/swarm" import ( "time" @@ -36,6 +36,10 @@ const ( TaskStateFailed TaskState = "failed" // TaskStateRejected REJECTED TaskStateRejected TaskState = "rejected" + // TaskStateRemove REMOVE + TaskStateRemove TaskState = "remove" + // TaskStateOrphaned ORPHANED + TaskStateOrphaned TaskState = "orphaned" ) // Task represents a task. @@ -56,10 +60,13 @@ type Task struct { // TaskSpec represents the spec of a task. type TaskSpec struct { - // ContainerSpec and PluginSpec are mutually exclusive. - // PluginSpec will only be used when the `Runtime` field is set to `plugin` - ContainerSpec *ContainerSpec `json:",omitempty"` - PluginSpec *runtime.PluginSpec `json:",omitempty"` + // ContainerSpec, NetworkAttachmentSpec, and PluginSpec are mutually exclusive. + // PluginSpec is only used when the `Runtime` field is set to `plugin` + // NetworkAttachmentSpec is used if the `Runtime` field is set to + // `attachment`. + ContainerSpec *ContainerSpec `json:",omitempty"` + PluginSpec *runtime.PluginSpec `json:",omitempty"` + NetworkAttachmentSpec *NetworkAttachmentSpec `json:",omitempty"` Resources *ResourceRequirements `json:",omitempty"` RestartPolicy *RestartPolicy `json:",omitempty"` @@ -120,6 +127,7 @@ type ResourceRequirements struct { type Placement struct { Constraints []string `json:",omitempty"` Preferences []PlacementPreference `json:",omitempty"` + MaxReplicas uint64 `json:",omitempty"` // Platforms stores all the platforms that the image can run on. // This field is used in the platform filter for scheduling. If empty, @@ -162,19 +170,19 @@ const ( // TaskStatus represents the status of a task. type TaskStatus struct { - Timestamp time.Time `json:",omitempty"` - State TaskState `json:",omitempty"` - Message string `json:",omitempty"` - Err string `json:",omitempty"` - ContainerStatus ContainerStatus `json:",omitempty"` - PortStatus PortStatus `json:",omitempty"` + Timestamp time.Time `json:",omitempty"` + State TaskState `json:",omitempty"` + Message string `json:",omitempty"` + Err string `json:",omitempty"` + ContainerStatus *ContainerStatus `json:",omitempty"` + PortStatus PortStatus `json:",omitempty"` } // ContainerStatus represents the status of a container. type ContainerStatus struct { - ContainerID string `json:",omitempty"` - PID int `json:",omitempty"` - ExitCode int `json:",omitempty"` + ContainerID string + PID int + ExitCode int } // PortStatus represents the port status of a task's host ports whose diff --git a/vendor/github.com/docker/docker/api/types/time/duration_convert.go b/vendor/github.com/docker/docker/api/types/time/duration_convert.go index 63e1eec19..84b6f0732 100644 --- a/vendor/github.com/docker/docker/api/types/time/duration_convert.go +++ b/vendor/github.com/docker/docker/api/types/time/duration_convert.go @@ -1,4 +1,4 @@ -package time +package time // import "github.com/docker/docker/api/types/time" import ( "strconv" diff --git a/vendor/github.com/docker/docker/api/types/time/timestamp.go b/vendor/github.com/docker/docker/api/types/time/timestamp.go index ed9c1168b..ea3495efe 100644 --- a/vendor/github.com/docker/docker/api/types/time/timestamp.go +++ b/vendor/github.com/docker/docker/api/types/time/timestamp.go @@ -1,4 +1,4 @@ -package time +package time // import "github.com/docker/docker/api/types/time" import ( "fmt" @@ -82,11 +82,14 @@ func GetTimestamp(value string, reference time.Time) (string, error) { } if err != nil { - // if there is a `-` then it's an RFC3339 like timestamp otherwise assume unixtimestamp + // if there is a `-` then it's an RFC3339 like timestamp if strings.Contains(value, "-") { return "", err // was probably an RFC3339 like timestamp but the parser failed with an error } - return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) + if _, _, err := parseTimestamp(value); err != nil { + return "", fmt.Errorf("failed to parse value as time or duration: %q", value) + } + return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) } return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil @@ -104,6 +107,10 @@ func ParseTimestamps(value string, def int64) (int64, int64, error) { if value == "" { return def, 0, nil } + return parseTimestamp(value) +} + +func parseTimestamp(value string) (int64, int64, error) { sa := strings.SplitN(value, ".", 2) s, err := strconv.ParseInt(sa[0], 10, 64) if err != nil { diff --git a/vendor/github.com/docker/docker/api/types/types.go b/vendor/github.com/docker/docker/api/types/types.go index f7ac77297..ed555aad2 100644 --- a/vendor/github.com/docker/docker/api/types/types.go +++ b/vendor/github.com/docker/docker/api/types/types.go @@ -1,4 +1,4 @@ -package types +package types // import "github.com/docker/docker/api/types" import ( "errors" @@ -102,14 +102,27 @@ type ContainerStats struct { // Ping contains response of Engine API: // GET "/_ping" type Ping struct { - APIVersion string - OSType string - Experimental bool + APIVersion string + OSType string + Experimental bool + BuilderVersion BuilderVersion +} + +// ComponentVersion describes the version information for a specific component. +type ComponentVersion struct { + Name string + Version string + Details map[string]string `json:",omitempty"` } // Version contains response of Engine API: // GET "/version" type Version struct { + Platform struct{ Name string } `json:",omitempty"` + Components []ComponentVersion `json:",omitempty"` + + // The following fields are deprecated, they relate to the Engine component and are kept for backwards compatibility + Version string APIVersion string `json:"ApiVersion"` MinAPIVersion string `json:"MinAPIVersion,omitempty"` @@ -133,6 +146,7 @@ type Commit struct { // GET "/info" type Info struct { ID string + Builder BuilderVersion Containers int ContainersRunning int ContainersPaused int @@ -145,10 +159,12 @@ type Info struct { MemoryLimit bool SwapLimit bool KernelMemory bool + KernelMemoryTCP bool CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` CPUShares bool CPUSet bool + PidsLimit bool IPv4Forwarding bool BridgeNfIptables bool BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` @@ -192,6 +208,8 @@ type Info struct { RuncCommit Commit InitCommit Commit SecurityOptions []string + ProductLicense string `json:",omitempty"` + Warnings []string } // KeyValue holds a key/value pair @@ -500,7 +518,8 @@ type DiskUsage struct { Images []*ImageSummary Containers []*Container Volumes []*Volume - BuilderSize int64 + BuildCache []*BuildCache + BuilderSize int64 // deprecated } // ContainersPruneReport contains the response for Engine API: @@ -527,6 +546,7 @@ type ImagesPruneReport struct { // BuildCachePruneReport contains the response for Engine API: // POST "/build/prune" type BuildCachePruneReport struct { + CachesDeleted []string SpaceReclaimed uint64 } @@ -573,3 +593,24 @@ type PushResult struct { type BuildResult struct { ID string } + +// BuildCache contains information about a build cache record +type BuildCache struct { + ID string + Parent string + Type string + Description string + InUse bool + Shared bool + Size int64 + CreatedAt time.Time + LastUsedAt *time.Time + UsageCount int +} + +// BuildCachePruneOptions hold parameters to prune the build cache +type BuildCachePruneOptions struct { + All bool + KeepStorage int64 + Filters filters.Args +} diff --git a/vendor/github.com/docker/docker/api/types/versions/compare.go b/vendor/github.com/docker/docker/api/types/versions/compare.go index 611d4fed6..8ccb0aa92 100644 --- a/vendor/github.com/docker/docker/api/types/versions/compare.go +++ b/vendor/github.com/docker/docker/api/types/versions/compare.go @@ -1,4 +1,4 @@ -package versions +package versions // import "github.com/docker/docker/api/types/versions" import ( "strconv" diff --git a/vendor/github.com/docker/docker/api/types/volume/volumes_create.go b/vendor/github.com/docker/docker/api/types/volume/volume_create.go index 9f70e43ca..f12e48612 100644 --- a/vendor/github.com/docker/docker/api/types/volume/volumes_create.go +++ b/vendor/github.com/docker/docker/api/types/volume/volume_create.go @@ -7,9 +7,9 @@ package volume // See hack/generate-swagger-api.sh // ---------------------------------------------------------------------------- -// VolumesCreateBody volumes create body -// swagger:model VolumesCreateBody -type VolumesCreateBody struct { +// VolumeCreateBody Volume configuration +// swagger:model VolumeCreateBody +type VolumeCreateBody struct { // Name of the volume driver to use. // Required: true diff --git a/vendor/github.com/docker/docker/api/types/volume/volumes_list.go b/vendor/github.com/docker/docker/api/types/volume/volume_list.go index 833dad933..020198f73 100644 --- a/vendor/github.com/docker/docker/api/types/volume/volumes_list.go +++ b/vendor/github.com/docker/docker/api/types/volume/volume_list.go @@ -9,9 +9,9 @@ package volume import "github.com/docker/docker/api/types" -// VolumesListOKBody volumes list o k body -// swagger:model VolumesListOKBody -type VolumesListOKBody struct { +// VolumeListOKBody Volume list response +// swagger:model VolumeListOKBody +type VolumeListOKBody struct { // List of volumes // Required: true diff --git a/vendor/github.com/docker/docker/client/README.md b/vendor/github.com/docker/docker/client/README.md index 059dfb3ce..992f18117 100644 --- a/vendor/github.com/docker/docker/client/README.md +++ b/vendor/github.com/docker/docker/client/README.md @@ -16,7 +16,7 @@ import ( ) func main() { - cli, err := client.NewEnvClient() + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { panic(err) } diff --git a/vendor/github.com/docker/docker/client/build_cancel.go b/vendor/github.com/docker/docker/client/build_cancel.go new file mode 100644 index 000000000..74df49508 --- /dev/null +++ b/vendor/github.com/docker/docker/client/build_cancel.go @@ -0,0 +1,20 @@ +package client // import "github.com/docker/docker/client" + +import ( + "context" + "net/url" +) + +// BuildCancel requests the daemon to cancel ongoing build request +func (cli *Client) BuildCancel(ctx context.Context, id string) error { + query := url.Values{} + query.Set("id", id) + + serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil) + if err != nil { + return err + } + defer ensureReaderClosed(serverResp) + + return nil +} diff --git a/vendor/github.com/docker/docker/client/build_prune.go b/vendor/github.com/docker/docker/client/build_prune.go index ccab115d3..42bbf99ef 100644 --- a/vendor/github.com/docker/docker/client/build_prune.go +++ b/vendor/github.com/docker/docker/client/build_prune.go @@ -1,22 +1,37 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" + "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" + "github.com/docker/docker/api/types/filters" + "github.com/pkg/errors" ) // BuildCachePrune requests the daemon to delete unused cache data -func (cli *Client) BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) { +func (cli *Client) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) { if err := cli.NewVersionError("1.31", "build prune"); err != nil { return nil, err } report := types.BuildCachePruneReport{} - serverResp, err := cli.post(ctx, "/build/prune", nil, nil, nil) + query := url.Values{} + if opts.All { + query.Set("all", "1") + } + query.Set("keep-storage", fmt.Sprintf("%d", opts.KeepStorage)) + filters, err := filters.ToJSON(opts.Filters) + if err != nil { + return nil, errors.Wrap(err, "prune could not marshal filters option") + } + query.Set("filters", filters) + + serverResp, err := cli.post(ctx, "/build/prune", query, nil, nil) + if err != nil { return nil, err } diff --git a/vendor/github.com/docker/docker/client/checkpoint_create.go b/vendor/github.com/docker/docker/client/checkpoint_create.go index 0effe498b..921024fe4 100644 --- a/vendor/github.com/docker/docker/client/checkpoint_create.go +++ b/vendor/github.com/docker/docker/client/checkpoint_create.go @@ -1,8 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // CheckpointCreate creates a checkpoint from the given container with the given name diff --git a/vendor/github.com/docker/docker/client/checkpoint_delete.go b/vendor/github.com/docker/docker/client/checkpoint_delete.go index e6e75588b..54f55fa76 100644 --- a/vendor/github.com/docker/docker/client/checkpoint_delete.go +++ b/vendor/github.com/docker/docker/client/checkpoint_delete.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // CheckpointDelete deletes the checkpoint with the given name from the given container diff --git a/vendor/github.com/docker/docker/client/checkpoint_list.go b/vendor/github.com/docker/docker/client/checkpoint_list.go index 9835bad5c..2b73fb553 100644 --- a/vendor/github.com/docker/docker/client/checkpoint_list.go +++ b/vendor/github.com/docker/docker/client/checkpoint_list.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // CheckpointList returns the checkpoints of the given container in the docker host diff --git a/vendor/github.com/docker/docker/client/client.go b/vendor/github.com/docker/docker/client/client.go index 893124853..f174d3ce7 100644 --- a/vendor/github.com/docker/docker/client/client.go +++ b/vendor/github.com/docker/docker/client/client.go @@ -23,7 +23,7 @@ For example, to list running containers (the equivalent of "docker ps"): ) func main() { - cli, err := client.NewEnvClient() + cli, err := client.NewClientWithOpts(client.FromEnv) if err != nil { panic(err) } @@ -39,24 +39,22 @@ For example, to list running containers (the equivalent of "docker ps"): } */ -package client +package client // import "github.com/docker/docker/client" import ( - "errors" + "context" "fmt" + "net" "net/http" "net/url" - "os" "path" - "path/filepath" "strings" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/docker/go-connections/sockets" - "github.com/docker/go-connections/tlsconfig" - "golang.org/x/net/context" + "github.com/pkg/errors" ) // ErrRedirect is the error returned by checkRedirect when the request is non-GET. @@ -102,99 +100,62 @@ func CheckRedirect(req *http.Request, via []*http.Request) error { return ErrRedirect } -// NewEnvClient initializes a new API client based on environment variables. -// Use DOCKER_HOST to set the url to the docker server. -// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. -// Use DOCKER_CERT_PATH to load the TLS certificates from. -// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. -func NewEnvClient() (*Client, error) { - var client *http.Client - if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { - options := tlsconfig.Options{ - CAFile: filepath.Join(dockerCertPath, "ca.pem"), - CertFile: filepath.Join(dockerCertPath, "cert.pem"), - KeyFile: filepath.Join(dockerCertPath, "key.pem"), - InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", - } - tlsc, err := tlsconfig.Client(options) - if err != nil { - return nil, err - } - - client = &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: tlsc, - }, - CheckRedirect: CheckRedirect, - } - } - - host := os.Getenv("DOCKER_HOST") - if host == "" { - host = DefaultDockerHost - } - version := os.Getenv("DOCKER_API_VERSION") - if version == "" { - version = api.DefaultVersion - } - - cli, err := NewClient(host, version, client, nil) - if err != nil { - return cli, err - } - if os.Getenv("DOCKER_API_VERSION") != "" { - cli.manualOverride = true - } - return cli, nil -} - -// NewClient initializes a new API client for the given host and API version. -// It uses the given http client as transport. +// NewClientWithOpts initializes a new API client with default values. It takes functors +// to modify values when creating it, like `NewClientWithOpts(WithVersion(…))` // It also initializes the custom http headers to add to each request. // // It won't send any version information if the version number is empty. It is // highly recommended that you set a version or your client may break if the // server is upgraded. -func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { - hostURL, err := ParseHostURL(host) +func NewClientWithOpts(ops ...func(*Client) error) (*Client, error) { + client, err := defaultHTTPClient(DefaultDockerHost) if err != nil { return nil, err } + c := &Client{ + host: DefaultDockerHost, + version: api.DefaultVersion, + client: client, + proto: defaultProto, + addr: defaultAddr, + } - if client != nil { - if _, ok := client.Transport.(http.RoundTripper); !ok { - return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport) - } - } else { - transport := new(http.Transport) - sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host) - client = &http.Client{ - Transport: transport, - CheckRedirect: CheckRedirect, + for _, op := range ops { + if err := op(c); err != nil { + return nil, err } } - scheme := "http" - tlsConfig := resolveTLSConfig(client.Transport) - if tlsConfig != nil { - // TODO(stevvooe): This isn't really the right way to write clients in Go. - // `NewClient` should probably only take an `*http.Client` and work from there. - // Unfortunately, the model of having a host-ish/url-thingy as the connection - // string has us confusing protocol and transport layers. We continue doing - // this to avoid breaking existing clients but this should be addressed. - scheme = "https" + if _, ok := c.client.Transport.(http.RoundTripper); !ok { + return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", c.client.Transport) + } + if c.scheme == "" { + c.scheme = "http" + + tlsConfig := resolveTLSConfig(c.client.Transport) + if tlsConfig != nil { + // TODO(stevvooe): This isn't really the right way to write clients in Go. + // `NewClient` should probably only take an `*http.Client` and work from there. + // Unfortunately, the model of having a host-ish/url-thingy as the connection + // string has us confusing protocol and transport layers. We continue doing + // this to avoid breaking existing clients but this should be addressed. + c.scheme = "https" + } } - // TODO: store URL instead of proto/addr/basePath - return &Client{ - scheme: scheme, - host: host, - proto: hostURL.Scheme, - addr: hostURL.Host, - basePath: hostURL.Path, - client: client, - version: version, - customHTTPHeaders: httpHeaders, + return c, nil +} + +func defaultHTTPClient(host string) (*http.Client, error) { + url, err := ParseHostURL(host) + if err != nil { + return nil, err + } + transport := new(http.Transport) + sockets.ConfigureTransport(transport, url.Scheme, url.Host) + return &http.Client{ + Transport: transport, + CheckRedirect: CheckRedirect, }, nil } @@ -259,15 +220,9 @@ func (cli *Client) DaemonHost() string { return cli.host } -// ParseHost parses a url string, validates the strings is a host url, and returns -// the parsed host as: protocol, address, and base path -// Deprecated: use ParseHostURL -func ParseHost(host string) (string, string, string, error) { - hostURL, err := ParseHostURL(host) - if err != nil { - return "", "", "", err - } - return hostURL.Scheme, hostURL.Host, hostURL.Path, nil +// HTTPClient returns a copy of the HTTP client bound to the server +func (cli *Client) HTTPClient() *http.Client { + return &*cli.client } // ParseHostURL parses a url string, validates the string is a host url, and @@ -305,6 +260,20 @@ func (cli *Client) CustomHTTPHeaders() map[string]string { } // SetCustomHTTPHeaders that will be set on every HTTP request made by the client. +// Deprecated: use WithHTTPHeaders when creating the client. func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) { cli.customHTTPHeaders = headers } + +// Dialer returns a dialer for a raw stream connection, with HTTP/1.1 header, that can be used for proxying the daemon connection. +// Used by `docker dial-stdio` (docker/cli#889). +func (cli *Client) Dialer() func(context.Context) (net.Conn, error) { + return func(ctx context.Context) (net.Conn, error) { + if transport, ok := cli.client.Transport.(*http.Transport); ok { + if transport.DialContext != nil && transport.TLSClientConfig == nil { + return transport.DialContext(ctx, cli.proto, cli.addr) + } + } + return fallbackDial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) + } +} diff --git a/vendor/github.com/docker/docker/client/client_deprecated.go b/vendor/github.com/docker/docker/client/client_deprecated.go new file mode 100644 index 000000000..54cdfc29a --- /dev/null +++ b/vendor/github.com/docker/docker/client/client_deprecated.go @@ -0,0 +1,23 @@ +package client + +import "net/http" + +// NewClient initializes a new API client for the given host and API version. +// It uses the given http client as transport. +// It also initializes the custom http headers to add to each request. +// +// It won't send any version information if the version number is empty. It is +// highly recommended that you set a version or your client may break if the +// server is upgraded. +// Deprecated: use NewClientWithOpts +func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) { + return NewClientWithOpts(WithHost(host), WithVersion(version), WithHTTPClient(client), WithHTTPHeaders(httpHeaders)) +} + +// NewEnvClient initializes a new API client based on environment variables. +// See FromEnv for a list of support environment variables. +// +// Deprecated: use NewClientWithOpts(FromEnv) +func NewEnvClient() (*Client, error) { + return NewClientWithOpts(FromEnv) +} diff --git a/vendor/github.com/docker/docker/client/client_unix.go b/vendor/github.com/docker/docker/client/client_unix.go index 89de892c8..3d24470ba 100644 --- a/vendor/github.com/docker/docker/client/client_unix.go +++ b/vendor/github.com/docker/docker/client/client_unix.go @@ -1,6 +1,9 @@ -// +build linux freebsd solaris openbsd darwin +// +build linux freebsd openbsd darwin -package client +package client // import "github.com/docker/docker/client" // DefaultDockerHost defines os specific default if DOCKER_HOST is unset const DefaultDockerHost = "unix:///var/run/docker.sock" + +const defaultProto = "unix" +const defaultAddr = "/var/run/docker.sock" diff --git a/vendor/github.com/docker/docker/client/client_windows.go b/vendor/github.com/docker/docker/client/client_windows.go index 07c0c7a77..c649e5441 100644 --- a/vendor/github.com/docker/docker/client/client_windows.go +++ b/vendor/github.com/docker/docker/client/client_windows.go @@ -1,4 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" // DefaultDockerHost defines os specific default if DOCKER_HOST is unset const DefaultDockerHost = "npipe:////./pipe/docker_engine" + +const defaultProto = "npipe" +const defaultAddr = "//./pipe/docker_engine" diff --git a/vendor/github.com/docker/docker/client/config_create.go b/vendor/github.com/docker/docker/client/config_create.go index bc4a952b2..c8b802ad3 100644 --- a/vendor/github.com/docker/docker/client/config_create.go +++ b/vendor/github.com/docker/docker/client/config_create.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ConfigCreate creates a new Config. diff --git a/vendor/github.com/docker/docker/client/config_inspect.go b/vendor/github.com/docker/docker/client/config_inspect.go index b44d6fdd7..4ac566ad8 100644 --- a/vendor/github.com/docker/docker/client/config_inspect.go +++ b/vendor/github.com/docker/docker/client/config_inspect.go @@ -1,16 +1,19 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ConfigInspectWithRaw returns the config information with raw data func (cli *Client) ConfigInspectWithRaw(ctx context.Context, id string) (swarm.Config, []byte, error) { + if id == "" { + return swarm.Config{}, nil, objectNotFoundError{object: "config", id: id} + } if err := cli.NewVersionError("1.30", "config inspect"); err != nil { return swarm.Config{}, nil, err } diff --git a/vendor/github.com/docker/docker/client/config_list.go b/vendor/github.com/docker/docker/client/config_list.go index 57febc9ff..2b9d54606 100644 --- a/vendor/github.com/docker/docker/client/config_list.go +++ b/vendor/github.com/docker/docker/client/config_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ConfigList returns the list of configs. diff --git a/vendor/github.com/docker/docker/client/config_remove.go b/vendor/github.com/docker/docker/client/config_remove.go index e025d44f7..a96871e98 100644 --- a/vendor/github.com/docker/docker/client/config_remove.go +++ b/vendor/github.com/docker/docker/client/config_remove.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // ConfigRemove removes a Config. func (cli *Client) ConfigRemove(ctx context.Context, id string) error { diff --git a/vendor/github.com/docker/docker/client/config_update.go b/vendor/github.com/docker/docker/client/config_update.go index 823751bb8..39e59cf85 100644 --- a/vendor/github.com/docker/docker/client/config_update.go +++ b/vendor/github.com/docker/docker/client/config_update.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ConfigUpdate attempts to update a Config diff --git a/vendor/github.com/docker/docker/client/container_attach.go b/vendor/github.com/docker/docker/client/container_attach.go index 0fdf3ed0c..88ba1ef63 100644 --- a/vendor/github.com/docker/docker/client/container_attach.go +++ b/vendor/github.com/docker/docker/client/container_attach.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerAttach attaches a connection to a container in the server. diff --git a/vendor/github.com/docker/docker/client/container_commit.go b/vendor/github.com/docker/docker/client/container_commit.go index b3b16abfd..377a2ea68 100644 --- a/vendor/github.com/docker/docker/client/container_commit.go +++ b/vendor/github.com/docker/docker/client/container_commit.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "errors" "net/url" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerCommit applies changes into a container and creates a new tagged image. diff --git a/vendor/github.com/docker/docker/client/container_copy.go b/vendor/github.com/docker/docker/client/container_copy.go index 30ba6803f..d706260ce 100644 --- a/vendor/github.com/docker/docker/client/container_copy.go +++ b/vendor/github.com/docker/docker/client/container_copy.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -10,8 +11,6 @@ import ( "path/filepath" "strings" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" ) @@ -23,17 +22,17 @@ func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path stri urlStr := "/containers/" + containerID + "/archive" response, err := cli.head(ctx, urlStr, query, nil) if err != nil { - return types.ContainerPathStat{}, err + return types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+path) } defer ensureReaderClosed(response) return getContainerPathStatFromHeader(response.header) } // CopyToContainer copies content into the container filesystem. -// Note that `content` must be a Reader for a TAR -func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error { +// Note that `content` must be a Reader for a TAR archive +func (cli *Client) CopyToContainer(ctx context.Context, containerID, dstPath string, content io.Reader, options types.CopyToContainerOptions) error { query := url.Values{} - query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API. + query.Set("path", filepath.ToSlash(dstPath)) // Normalize the paths used in the API. // Do not allow for an existing directory to be overwritten by a non-directory and vice versa. if !options.AllowOverwriteDirWithFile { query.Set("noOverwriteDirNonDir", "true") @@ -43,11 +42,11 @@ func (cli *Client) CopyToContainer(ctx context.Context, container, path string, query.Set("copyUIDGID", "true") } - apiPath := "/containers/" + container + "/archive" + apiPath := "/containers/" + containerID + "/archive" response, err := cli.putRaw(ctx, apiPath, query, content, nil) if err != nil { - return err + return wrapResponseError(err, response, "container:path", containerID+":"+dstPath) } defer ensureReaderClosed(response) @@ -59,15 +58,15 @@ func (cli *Client) CopyToContainer(ctx context.Context, container, path string, } // CopyFromContainer gets the content from the container and returns it as a Reader -// to manipulate it in the host. It's up to the caller to close the reader. -func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { +// for a TAR archive to manipulate it in the host. It's up to the caller to close the reader. +func (cli *Client) CopyFromContainer(ctx context.Context, containerID, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { query := make(url.Values, 1) query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API. - apiPath := "/containers/" + container + "/archive" + apiPath := "/containers/" + containerID + "/archive" response, err := cli.get(ctx, apiPath, query, nil) if err != nil { - return nil, types.ContainerPathStat{}, err + return nil, types.ContainerPathStat{}, wrapResponseError(err, response, "container:path", containerID+":"+srcPath) } if response.statusCode != http.StatusOK { diff --git a/vendor/github.com/docker/docker/client/container_create.go b/vendor/github.com/docker/docker/client/container_create.go index bd817e7fd..d269a6189 100644 --- a/vendor/github.com/docker/docker/client/container_create.go +++ b/vendor/github.com/docker/docker/client/container_create.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "strings" @@ -8,7 +9,6 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/versions" - "golang.org/x/net/context" ) type configWrapper struct { diff --git a/vendor/github.com/docker/docker/client/container_diff.go b/vendor/github.com/docker/docker/client/container_diff.go index 884dc9fee..3b7c90c96 100644 --- a/vendor/github.com/docker/docker/client/container_diff.go +++ b/vendor/github.com/docker/docker/client/container_diff.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/container" - "golang.org/x/net/context" ) // ContainerDiff shows differences in a container filesystem since it was started. diff --git a/vendor/github.com/docker/docker/client/container_exec.go b/vendor/github.com/docker/docker/client/container_exec.go index 29670d02e..535536b1e 100644 --- a/vendor/github.com/docker/docker/client/container_exec.go +++ b/vendor/github.com/docker/docker/client/container_exec.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerExecCreate creates a new exec configuration to run an exec process. diff --git a/vendor/github.com/docker/docker/client/container_export.go b/vendor/github.com/docker/docker/client/container_export.go index 52194f3d3..d0c0a5cba 100644 --- a/vendor/github.com/docker/docker/client/container_export.go +++ b/vendor/github.com/docker/docker/client/container_export.go @@ -1,10 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" - - "golang.org/x/net/context" ) // ContainerExport retrieves the raw contents of a container diff --git a/vendor/github.com/docker/docker/client/container_inspect.go b/vendor/github.com/docker/docker/client/container_inspect.go index a15db14be..e34bb16a2 100644 --- a/vendor/github.com/docker/docker/client/container_inspect.go +++ b/vendor/github.com/docker/docker/client/container_inspect.go @@ -1,30 +1,36 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerInspect returns the container information. func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { + if containerID == "" { + return types.ContainerJSON{}, objectNotFoundError{object: "container", id: containerID} + } serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) if err != nil { return types.ContainerJSON{}, wrapResponseError(err, serverResp, "container", containerID) } + defer ensureReaderClosed(serverResp) var response types.ContainerJSON err = json.NewDecoder(serverResp.body).Decode(&response) - ensureReaderClosed(serverResp) return response, err } // ContainerInspectWithRaw returns the container information and its raw representation. func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) { + if containerID == "" { + return types.ContainerJSON{}, nil, objectNotFoundError{object: "container", id: containerID} + } query := url.Values{} if getSize { query.Set("size", "1") diff --git a/vendor/github.com/docker/docker/client/container_kill.go b/vendor/github.com/docker/docker/client/container_kill.go index 29f80c73a..4d6f1d23d 100644 --- a/vendor/github.com/docker/docker/client/container_kill.go +++ b/vendor/github.com/docker/docker/client/container_kill.go @@ -1,9 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" - - "golang.org/x/net/context" ) // ContainerKill terminates the container process but does not remove the container from the docker host. diff --git a/vendor/github.com/docker/docker/client/container_list.go b/vendor/github.com/docker/docker/client/container_list.go index 439891219..9c218e221 100644 --- a/vendor/github.com/docker/docker/client/container_list.go +++ b/vendor/github.com/docker/docker/client/container_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "strconv" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // ContainerList returns the list of containers in the docker host. diff --git a/vendor/github.com/docker/docker/client/container_logs.go b/vendor/github.com/docker/docker/client/container_logs.go index 0f32e9f12..5b6541f03 100644 --- a/vendor/github.com/docker/docker/client/container_logs.go +++ b/vendor/github.com/docker/docker/client/container_logs.go @@ -1,14 +1,14 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "time" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" timetypes "github.com/docker/docker/api/types/time" + "github.com/pkg/errors" ) // ContainerLogs returns the logs generated by a container in an io.ReadCloser. @@ -46,11 +46,19 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, time.Now()) if err != nil { - return nil, err + return nil, errors.Wrap(err, `invalid value for "since"`) } query.Set("since", ts) } + if options.Until != "" { + ts, err := timetypes.GetTimestamp(options.Until, time.Now()) + if err != nil { + return nil, errors.Wrap(err, `invalid value for "until"`) + } + query.Set("until", ts) + } + if options.Timestamps { query.Set("timestamps", "1") } @@ -66,7 +74,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil) if err != nil { - return nil, err + return nil, wrapResponseError(err, resp, "container", container) } return resp.body, nil } diff --git a/vendor/github.com/docker/docker/client/container_pause.go b/vendor/github.com/docker/docker/client/container_pause.go index 412067a78..5e7271a37 100644 --- a/vendor/github.com/docker/docker/client/container_pause.go +++ b/vendor/github.com/docker/docker/client/container_pause.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // ContainerPause pauses the main process of a given container without terminating it. func (cli *Client) ContainerPause(ctx context.Context, containerID string) error { diff --git a/vendor/github.com/docker/docker/client/container_prune.go b/vendor/github.com/docker/docker/client/container_prune.go index b58217086..14f88d93b 100644 --- a/vendor/github.com/docker/docker/client/container_prune.go +++ b/vendor/github.com/docker/docker/client/container_prune.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // ContainersPrune requests the daemon to delete unused data diff --git a/vendor/github.com/docker/docker/client/container_remove.go b/vendor/github.com/docker/docker/client/container_remove.go index 070108bf3..ab4cfc16f 100644 --- a/vendor/github.com/docker/docker/client/container_remove.go +++ b/vendor/github.com/docker/docker/client/container_remove.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerRemove kills and removes a container from the docker host. diff --git a/vendor/github.com/docker/docker/client/container_rename.go b/vendor/github.com/docker/docker/client/container_rename.go index 0e718da7c..240fdf552 100644 --- a/vendor/github.com/docker/docker/client/container_rename.go +++ b/vendor/github.com/docker/docker/client/container_rename.go @@ -1,9 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" - - "golang.org/x/net/context" ) // ContainerRename changes the name of a given container. diff --git a/vendor/github.com/docker/docker/client/container_resize.go b/vendor/github.com/docker/docker/client/container_resize.go index 66c3cc194..a9d4c0c79 100644 --- a/vendor/github.com/docker/docker/client/container_resize.go +++ b/vendor/github.com/docker/docker/client/container_resize.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "strconv" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerResize changes the size of the tty for a container. diff --git a/vendor/github.com/docker/docker/client/container_restart.go b/vendor/github.com/docker/docker/client/container_restart.go index 74d7455f0..41e421969 100644 --- a/vendor/github.com/docker/docker/client/container_restart.go +++ b/vendor/github.com/docker/docker/client/container_restart.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "time" timetypes "github.com/docker/docker/api/types/time" - "golang.org/x/net/context" ) // ContainerRestart stops and starts a container again. diff --git a/vendor/github.com/docker/docker/client/container_start.go b/vendor/github.com/docker/docker/client/container_start.go index b1f08de41..c2e0b15dc 100644 --- a/vendor/github.com/docker/docker/client/container_start.go +++ b/vendor/github.com/docker/docker/client/container_start.go @@ -1,10 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/container_stats.go b/vendor/github.com/docker/docker/client/container_stats.go index 4758c66e3..6ef44c774 100644 --- a/vendor/github.com/docker/docker/client/container_stats.go +++ b/vendor/github.com/docker/docker/client/container_stats.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ContainerStats returns near realtime stats for a given container. diff --git a/vendor/github.com/docker/docker/client/container_stop.go b/vendor/github.com/docker/docker/client/container_stop.go index b5418ae8c..629d7ab64 100644 --- a/vendor/github.com/docker/docker/client/container_stop.go +++ b/vendor/github.com/docker/docker/client/container_stop.go @@ -1,15 +1,20 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "time" timetypes "github.com/docker/docker/api/types/time" - "golang.org/x/net/context" ) -// ContainerStop stops a container without terminating the process. -// The process is blocked until the container stops or the timeout expires. +// ContainerStop stops a container. In case the container fails to stop +// gracefully within a time frame specified by the timeout argument, +// it is forcefully terminated (killed). +// +// If the timeout is nil, the container's StopTimeout value is used, if set, +// otherwise the engine default. A negative timeout value can be specified, +// meaning no timeout, i.e. no forceful termination is performed. func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error { query := url.Values{} if timeout != nil { diff --git a/vendor/github.com/docker/docker/client/container_top.go b/vendor/github.com/docker/docker/client/container_top.go index 9689123a4..9c9fce7a0 100644 --- a/vendor/github.com/docker/docker/client/container_top.go +++ b/vendor/github.com/docker/docker/client/container_top.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "strings" "github.com/docker/docker/api/types/container" - "golang.org/x/net/context" ) // ContainerTop shows process information from within a container. diff --git a/vendor/github.com/docker/docker/client/container_unpause.go b/vendor/github.com/docker/docker/client/container_unpause.go index 5c7621125..1d8f87316 100644 --- a/vendor/github.com/docker/docker/client/container_unpause.go +++ b/vendor/github.com/docker/docker/client/container_unpause.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // ContainerUnpause resumes the process execution within a container func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error { diff --git a/vendor/github.com/docker/docker/client/container_update.go b/vendor/github.com/docker/docker/client/container_update.go index 5082f22df..14e7f23df 100644 --- a/vendor/github.com/docker/docker/client/container_update.go +++ b/vendor/github.com/docker/docker/client/container_update.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types/container" - "golang.org/x/net/context" ) // ContainerUpdate updates resources of a container diff --git a/vendor/github.com/docker/docker/client/container_wait.go b/vendor/github.com/docker/docker/client/container_wait.go index 854c6c053..6ab8c1da9 100644 --- a/vendor/github.com/docker/docker/client/container_wait.go +++ b/vendor/github.com/docker/docker/client/container_wait.go @@ -1,11 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" - "golang.org/x/net/context" - "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/versions" ) diff --git a/vendor/github.com/docker/docker/client/disk_usage.go b/vendor/github.com/docker/docker/client/disk_usage.go index 03c80b39a..8eb30eb5d 100644 --- a/vendor/github.com/docker/docker/client/disk_usage.go +++ b/vendor/github.com/docker/docker/client/disk_usage.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // DiskUsage requests the current data usage from the daemon diff --git a/vendor/github.com/docker/docker/client/distribution_inspect.go b/vendor/github.com/docker/docker/client/distribution_inspect.go index aa5bc6a6c..7245bbeed 100644 --- a/vendor/github.com/docker/docker/client/distribution_inspect.go +++ b/vendor/github.com/docker/docker/client/distribution_inspect.go @@ -1,17 +1,20 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" registrytypes "github.com/docker/docker/api/types/registry" - "golang.org/x/net/context" ) // DistributionInspect returns the image digest with full Manifest func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegistryAuth string) (registrytypes.DistributionInspect, error) { // Contact the registry to retrieve digest and platform information var distributionInspect registrytypes.DistributionInspect + if image == "" { + return distributionInspect, objectNotFoundError{object: "distribution", id: image} + } if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil { return distributionInspect, err diff --git a/vendor/github.com/docker/docker/client/errors.go b/vendor/github.com/docker/docker/client/errors.go index e41b728d3..0461af329 100644 --- a/vendor/github.com/docker/docker/client/errors.go +++ b/vendor/github.com/docker/docker/client/errors.go @@ -1,8 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( "fmt" - "net/http" "github.com/docker/docker/api/types/versions" diff --git a/vendor/github.com/docker/docker/client/events.go b/vendor/github.com/docker/docker/client/events.go index af47aefa7..6e5653895 100644 --- a/vendor/github.com/docker/docker/client/events.go +++ b/vendor/github.com/docker/docker/client/events.go @@ -1,12 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "time" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" diff --git a/vendor/github.com/docker/docker/client/hijack.go b/vendor/github.com/docker/docker/client/hijack.go index d04cebdcf..0ac8248f2 100644 --- a/vendor/github.com/docker/docker/client/hijack.go +++ b/vendor/github.com/docker/docker/client/hijack.go @@ -1,37 +1,21 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bufio" + "context" "crypto/tls" "fmt" "net" "net/http" "net/http/httputil" "net/url" - "strings" "time" "github.com/docker/docker/api/types" "github.com/docker/go-connections/sockets" "github.com/pkg/errors" - "golang.org/x/net/context" ) -// tlsClientCon holds tls information and a dialed connection. -type tlsClientCon struct { - *tls.Conn - rawConn net.Conn -} - -func (c *tlsClientCon) CloseWrite() error { - // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it - // on its underlying connection. - if conn, ok := c.rawConn.(types.CloseWriter); ok { - return conn.CloseWrite() - } - return nil -} - // postHijacked sends a POST request and hijacks the connection. func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) { bodyEncoded, err := encodeData(body) @@ -46,7 +30,7 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu } req = cli.addHeaders(req, headers) - conn, err := cli.setupHijackConn(req, "tcp") + conn, err := cli.setupHijackConn(ctx, req, "tcp") if err != nil { return types.HijackedResponse{}, err } @@ -54,96 +38,11 @@ func (cli *Client) postHijacked(ctx context.Context, path string, query url.Valu return types.HijackedResponse{Conn: conn, Reader: bufio.NewReader(conn)}, err } -func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) { - return tlsDialWithDialer(new(net.Dialer), network, addr, config) -} - -// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in -// order to return our custom tlsClientCon struct which holds both the tls.Conn -// object _and_ its underlying raw connection. The rationale for this is that -// we need to be able to close the write end of the connection when attaching, -// which tls.Conn does not provide. -func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { - // We want the Timeout and Deadline values from dialer to cover the - // whole process: TCP connection and TLS handshake. This means that we - // also need to start our own timers now. - timeout := dialer.Timeout - - if !dialer.Deadline.IsZero() { - deadlineTimeout := time.Until(dialer.Deadline) - if timeout == 0 || deadlineTimeout < timeout { - timeout = deadlineTimeout - } - } - - var errChannel chan error - - if timeout != 0 { - errChannel = make(chan error, 2) - time.AfterFunc(timeout, func() { - errChannel <- errors.New("") - }) - } - - proxyDialer, err := sockets.DialerFromEnvironment(dialer) - if err != nil { - return nil, err - } - - rawConn, err := proxyDialer.Dial(network, addr) - if err != nil { - return nil, err - } - // When we set up a TCP connection for hijack, there could be long periods - // of inactivity (a long running command with no output) that in certain - // network setups may cause ECONNTIMEOUT, leaving the client in an unknown - // state. Setting TCP KeepAlive on the socket connection will prohibit - // ECONNTIMEOUT unless the socket connection truly is broken - if tcpConn, ok := rawConn.(*net.TCPConn); ok { - tcpConn.SetKeepAlive(true) - tcpConn.SetKeepAlivePeriod(30 * time.Second) - } - - colonPos := strings.LastIndex(addr, ":") - if colonPos == -1 { - colonPos = len(addr) - } - hostname := addr[:colonPos] - - // If no ServerName is set, infer the ServerName - // from the hostname we're connecting to. - if config.ServerName == "" { - // Make a copy to avoid polluting argument or default. - config = tlsConfigClone(config) - config.ServerName = hostname - } - - conn := tls.Client(rawConn, config) - - if timeout == 0 { - err = conn.Handshake() - } else { - go func() { - errChannel <- conn.Handshake() - }() - - err = <-errChannel - } - - if err != nil { - rawConn.Close() - return nil, err - } - - // This is Docker difference with standard's crypto/tls package: returned a - // wrapper which holds both the TLS and raw connections. - return &tlsClientCon{conn, rawConn}, nil -} - -func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { +// fallbackDial is used when WithDialer() was not called. +// See cli.Dialer(). +func fallbackDial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { if tlsConfig != nil && proto != "unix" && proto != "npipe" { - // Notice this isn't Go standard's tls.Dial function - return tlsDial(proto, addr, tlsConfig) + return tls.Dial(proto, addr, tlsConfig) } if proto == "npipe" { return sockets.DialPipe(addr, 32*time.Second) @@ -151,12 +50,13 @@ func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) { return net.Dial(proto, addr) } -func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, error) { +func (cli *Client) setupHijackConn(ctx context.Context, req *http.Request, proto string) (net.Conn, error) { req.Host = cli.addr req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", proto) - conn, err := dial(cli.proto, cli.addr, resolveTLSConfig(cli.client.Transport)) + dialer := cli.Dialer() + conn, err := dialer(ctx) if err != nil { return nil, errors.Wrap(err, "cannot connect to the Docker daemon. Is 'docker daemon' running on this host?") } @@ -188,8 +88,14 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, e c, br := clientconn.Hijack() if br.Buffered() > 0 { - // If there is buffered content, wrap the connection - c = &hijackedConn{c, br} + // If there is buffered content, wrap the connection. We return an + // object that implements CloseWrite iff the underlying connection + // implements it. + if _, ok := c.(types.CloseWriter); ok { + c = &hijackedConnCloseWriter{&hijackedConn{c, br}} + } else { + c = &hijackedConn{c, br} + } } else { br.Reset(nil) } @@ -197,6 +103,10 @@ func (cli *Client) setupHijackConn(req *http.Request, proto string) (net.Conn, e return c, nil } +// hijackedConn wraps a net.Conn and is returned by setupHijackConn in the case +// that a) there was already buffered data in the http layer when Hijack() was +// called, and b) the underlying net.Conn does *not* implement CloseWrite(). +// hijackedConn does not implement CloseWrite() either. type hijackedConn struct { net.Conn r *bufio.Reader @@ -205,3 +115,18 @@ type hijackedConn struct { func (c *hijackedConn) Read(b []byte) (int, error) { return c.r.Read(b) } + +// hijackedConnCloseWriter is a hijackedConn which additionally implements +// CloseWrite(). It is returned by setupHijackConn in the case that a) there +// was already buffered data in the http layer when Hijack() was called, and b) +// the underlying net.Conn *does* implement CloseWrite(). +type hijackedConnCloseWriter struct { + *hijackedConn +} + +var _ types.CloseWriter = &hijackedConnCloseWriter{} + +func (c *hijackedConnCloseWriter) CloseWrite() error { + conn := c.Conn.(types.CloseWriter) + return conn.CloseWrite() +} diff --git a/vendor/github.com/docker/docker/client/image_build.go b/vendor/github.com/docker/docker/client/image_build.go index cd0f54d13..9add3c10b 100644 --- a/vendor/github.com/docker/docker/client/image_build.go +++ b/vendor/github.com/docker/docker/client/image_build.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/base64" "encoding/json" "io" @@ -9,8 +10,6 @@ import ( "strconv" "strings" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" ) @@ -31,12 +30,6 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio } headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) - if options.Platform != "" { - if err := cli.NewVersionError("1.32", "platform"); err != nil { - return types.ImageBuildResponse{}, err - } - query.Set("platform", options.Platform) - } headers.Set("Content-Type", "application/x-tar") serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) @@ -132,7 +125,14 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur query.Set("session", options.SessionID) } if options.Platform != "" { + if err := cli.NewVersionError("1.32", "platform"); err != nil { + return query, err + } query.Set("platform", strings.ToLower(options.Platform)) } + if options.BuildID != "" { + query.Set("buildid", options.BuildID) + } + query.Set("version", string(options.Version)) return query, nil } diff --git a/vendor/github.com/docker/docker/client/image_create.go b/vendor/github.com/docker/docker/client/image_create.go index fb5447b9b..239380474 100644 --- a/vendor/github.com/docker/docker/client/image_create.go +++ b/vendor/github.com/docker/docker/client/image_create.go @@ -1,12 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "strings" - "golang.org/x/net/context" - "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/image_history.go b/vendor/github.com/docker/docker/client/image_history.go index 7b4babcba..0151b9517 100644 --- a/vendor/github.com/docker/docker/client/image_history.go +++ b/vendor/github.com/docker/docker/client/image_history.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/image" - "golang.org/x/net/context" ) // ImageHistory returns the changes in an image in history format. diff --git a/vendor/github.com/docker/docker/client/image_import.go b/vendor/github.com/docker/docker/client/image_import.go index ab55ddbac..c2972ea95 100644 --- a/vendor/github.com/docker/docker/client/image_import.go +++ b/vendor/github.com/docker/docker/client/image_import.go @@ -1,12 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "strings" - "golang.org/x/net/context" - "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/image_inspect.go b/vendor/github.com/docker/docker/client/image_inspect.go index 1bc591990..2f8f6d2f1 100644 --- a/vendor/github.com/docker/docker/client/image_inspect.go +++ b/vendor/github.com/docker/docker/client/image_inspect.go @@ -1,16 +1,19 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ImageInspectWithRaw returns the image information and its raw representation. func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) { + if imageID == "" { + return types.ImageInspect{}, nil, objectNotFoundError{object: "image", id: imageID} + } serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil) if err != nil { return types.ImageInspect{}, nil, wrapResponseError(err, serverResp, "image", imageID) diff --git a/vendor/github.com/docker/docker/client/image_list.go b/vendor/github.com/docker/docker/client/image_list.go index f26464f67..32fae27b3 100644 --- a/vendor/github.com/docker/docker/client/image_list.go +++ b/vendor/github.com/docker/docker/client/image_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/versions" - "golang.org/x/net/context" ) // ImageList returns a list of images in the docker host. diff --git a/vendor/github.com/docker/docker/client/image_load.go b/vendor/github.com/docker/docker/client/image_load.go index 77aaf1af3..91016e493 100644 --- a/vendor/github.com/docker/docker/client/image_load.go +++ b/vendor/github.com/docker/docker/client/image_load.go @@ -1,11 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/image_prune.go b/vendor/github.com/docker/docker/client/image_prune.go index 5ef98b7f0..78ee3f6c4 100644 --- a/vendor/github.com/docker/docker/client/image_prune.go +++ b/vendor/github.com/docker/docker/client/image_prune.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // ImagesPrune requests the daemon to delete unused data diff --git a/vendor/github.com/docker/docker/client/image_pull.go b/vendor/github.com/docker/docker/client/image_pull.go index 92942d2e5..d97aacf8c 100644 --- a/vendor/github.com/docker/docker/client/image_pull.go +++ b/vendor/github.com/docker/docker/client/image_pull.go @@ -1,13 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/http" "net/url" "strings" - "golang.org/x/net/context" - "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/image_push.go b/vendor/github.com/docker/docker/client/image_push.go index 410d2fb91..a15871c2b 100644 --- a/vendor/github.com/docker/docker/client/image_push.go +++ b/vendor/github.com/docker/docker/client/image_push.go @@ -1,13 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "errors" "io" "net/http" "net/url" - "golang.org/x/net/context" - "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" ) diff --git a/vendor/github.com/docker/docker/client/image_remove.go b/vendor/github.com/docker/docker/client/image_remove.go index 81d6c5438..45d6e6f0d 100644 --- a/vendor/github.com/docker/docker/client/image_remove.go +++ b/vendor/github.com/docker/docker/client/image_remove.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ImageRemove removes an image from the docker host. diff --git a/vendor/github.com/docker/docker/client/image_save.go b/vendor/github.com/docker/docker/client/image_save.go index ecac880a3..d1314e4b2 100644 --- a/vendor/github.com/docker/docker/client/image_save.go +++ b/vendor/github.com/docker/docker/client/image_save.go @@ -1,10 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" - - "golang.org/x/net/context" ) // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. diff --git a/vendor/github.com/docker/docker/client/image_search.go b/vendor/github.com/docker/docker/client/image_search.go index 5566e9255..176de3c58 100644 --- a/vendor/github.com/docker/docker/client/image_search.go +++ b/vendor/github.com/docker/docker/client/image_search.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "net/http" @@ -9,7 +10,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/registry" - "golang.org/x/net/context" ) // ImageSearch makes the docker host to search by a term in a remote registry. diff --git a/vendor/github.com/docker/docker/client/image_tag.go b/vendor/github.com/docker/docker/client/image_tag.go index 8924f71eb..5652bfc25 100644 --- a/vendor/github.com/docker/docker/client/image_tag.go +++ b/vendor/github.com/docker/docker/client/image_tag.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/distribution/reference" "github.com/pkg/errors" - "golang.org/x/net/context" ) // ImageTag tags an image in the docker host diff --git a/vendor/github.com/docker/docker/client/info.go b/vendor/github.com/docker/docker/client/info.go index ac0796122..121f256ab 100644 --- a/vendor/github.com/docker/docker/client/info.go +++ b/vendor/github.com/docker/docker/client/info.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // Info returns information about the docker server. diff --git a/vendor/github.com/docker/docker/client/interface.go b/vendor/github.com/docker/docker/client/interface.go index dd8b388cf..d190f8e58 100644 --- a/vendor/github.com/docker/docker/client/interface.go +++ b/vendor/github.com/docker/docker/client/interface.go @@ -1,20 +1,21 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net" + "net/http" "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" - "github.com/docker/docker/api/types/network" + networktypes "github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" volumetypes "github.com/docker/docker/api/types/volume" - "golang.org/x/net/context" ) // CommonAPIClient is the common methods between stable and experimental versions of APIClient. @@ -33,18 +34,21 @@ type CommonAPIClient interface { VolumeAPIClient ClientVersion() string DaemonHost() string + HTTPClient() *http.Client ServerVersion(ctx context.Context) (types.Version, error) NegotiateAPIVersion(ctx context.Context) NegotiateAPIVersionPing(types.Ping) DialSession(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) + Dialer() func(context.Context) (net.Conn, error) + Close() error } // ContainerAPIClient defines API client methods for the containers type ContainerAPIClient interface { ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.IDResponse, error) - ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error) - ContainerDiff(ctx context.Context, container string) ([]container.ContainerChangeResponseItem, error) + ContainerCreate(ctx context.Context, config *containertypes.Config, hostConfig *containertypes.HostConfig, networkingConfig *networktypes.NetworkingConfig, containerName string) (containertypes.ContainerCreateCreatedBody, error) + ContainerDiff(ctx context.Context, container string) ([]containertypes.ContainerChangeResponseItem, error) ContainerExecAttach(ctx context.Context, execID string, config types.ExecStartCheck) (types.HijackedResponse, error) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) @@ -65,10 +69,10 @@ type ContainerAPIClient interface { ContainerStats(ctx context.Context, container string, stream bool) (types.ContainerStats, error) ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error ContainerStop(ctx context.Context, container string, timeout *time.Duration) error - ContainerTop(ctx context.Context, container string, arguments []string) (container.ContainerTopOKBody, error) + ContainerTop(ctx context.Context, container string, arguments []string) (containertypes.ContainerTopOKBody, error) ContainerUnpause(ctx context.Context, container string) error - ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.ContainerUpdateOKBody, error) - ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) + ContainerUpdate(ctx context.Context, container string, updateConfig containertypes.UpdateConfig) (containertypes.ContainerUpdateOKBody, error) + ContainerWait(ctx context.Context, container string, condition containertypes.WaitCondition) (<-chan containertypes.ContainerWaitOKBody, <-chan error) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) @@ -82,7 +86,8 @@ type DistributionAPIClient interface { // ImageAPIClient defines API client methods for the images type ImageAPIClient interface { ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) - BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) + BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) + BuildCancel(ctx context.Context, id string) error ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) @@ -100,13 +105,13 @@ type ImageAPIClient interface { // NetworkAPIClient defines API client methods for the networks type NetworkAPIClient interface { - NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error + NetworkConnect(ctx context.Context, network, container string, config *networktypes.EndpointSettings) error NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) - NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error - NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) - NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) + NetworkDisconnect(ctx context.Context, network, container string, force bool) error + NetworkInspect(ctx context.Context, network string, options types.NetworkInspectOptions) (types.NetworkResource, error) + NetworkInspectWithRaw(ctx context.Context, network string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) - NetworkRemove(ctx context.Context, networkID string) error + NetworkRemove(ctx context.Context, network string) error NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) } @@ -167,10 +172,10 @@ type SystemAPIClient interface { // VolumeAPIClient defines API client methods for the volumes type VolumeAPIClient interface { - VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) + VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) - VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) + VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumeListOKBody, error) VolumeRemove(ctx context.Context, volumeID string, force bool) error VolumesPrune(ctx context.Context, pruneFilter filters.Args) (types.VolumesPruneReport, error) } diff --git a/vendor/github.com/docker/docker/client/interface_experimental.go b/vendor/github.com/docker/docker/client/interface_experimental.go index 51da98ecd..402ffb512 100644 --- a/vendor/github.com/docker/docker/client/interface_experimental.go +++ b/vendor/github.com/docker/docker/client/interface_experimental.go @@ -1,8 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) type apiClientExperimental interface { diff --git a/vendor/github.com/docker/docker/client/interface_stable.go b/vendor/github.com/docker/docker/client/interface_stable.go index cc90a3cbb..5502cd742 100644 --- a/vendor/github.com/docker/docker/client/interface_stable.go +++ b/vendor/github.com/docker/docker/client/interface_stable.go @@ -1,4 +1,4 @@ -package client +package client // import "github.com/docker/docker/client" // APIClient is an interface that clients that talk with a docker server must implement. type APIClient interface { diff --git a/vendor/github.com/docker/docker/client/login.go b/vendor/github.com/docker/docker/client/login.go index 79219ff59..7d6618190 100644 --- a/vendor/github.com/docker/docker/client/login.go +++ b/vendor/github.com/docker/docker/client/login.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/http" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/registry" - "golang.org/x/net/context" ) // RegistryLogin authenticates the docker server with a given docker registry. diff --git a/vendor/github.com/docker/docker/client/network_connect.go b/vendor/github.com/docker/docker/client/network_connect.go index c022c17b5..571894613 100644 --- a/vendor/github.com/docker/docker/client/network_connect.go +++ b/vendor/github.com/docker/docker/client/network_connect.go @@ -1,9 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" - "golang.org/x/net/context" ) // NetworkConnect connects a container to an existent network in the docker host. diff --git a/vendor/github.com/docker/docker/client/network_create.go b/vendor/github.com/docker/docker/client/network_create.go index 4067a541f..41da2ac61 100644 --- a/vendor/github.com/docker/docker/client/network_create.go +++ b/vendor/github.com/docker/docker/client/network_create.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // NetworkCreate creates a new network in the docker host. diff --git a/vendor/github.com/docker/docker/client/network_disconnect.go b/vendor/github.com/docker/docker/client/network_disconnect.go index 24b58e3c1..dd1567665 100644 --- a/vendor/github.com/docker/docker/client/network_disconnect.go +++ b/vendor/github.com/docker/docker/client/network_disconnect.go @@ -1,8 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // NetworkDisconnect disconnects a container from an existent network in the docker host. diff --git a/vendor/github.com/docker/docker/client/network_inspect.go b/vendor/github.com/docker/docker/client/network_inspect.go index afabe6597..025f6d875 100644 --- a/vendor/github.com/docker/docker/client/network_inspect.go +++ b/vendor/github.com/docker/docker/client/network_inspect.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // NetworkInspect returns the information for a specific network configured in the docker host. @@ -18,6 +18,9 @@ func (cli *Client) NetworkInspect(ctx context.Context, networkID string, options // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) { + if networkID == "" { + return types.NetworkResource{}, nil, objectNotFoundError{object: "network", id: networkID} + } var ( networkResource types.NetworkResource resp serverResponse diff --git a/vendor/github.com/docker/docker/client/network_list.go b/vendor/github.com/docker/docker/client/network_list.go index e566a93e2..f16b2f562 100644 --- a/vendor/github.com/docker/docker/client/network_list.go +++ b/vendor/github.com/docker/docker/client/network_list.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // NetworkList returns the list of networks configured in the docker host. diff --git a/vendor/github.com/docker/docker/client/network_prune.go b/vendor/github.com/docker/docker/client/network_prune.go index 7352a7f0c..6418b8b60 100644 --- a/vendor/github.com/docker/docker/client/network_prune.go +++ b/vendor/github.com/docker/docker/client/network_prune.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // NetworksPrune requests the daemon to delete unused networks diff --git a/vendor/github.com/docker/docker/client/network_remove.go b/vendor/github.com/docker/docker/client/network_remove.go index 0811b5b51..12741437b 100644 --- a/vendor/github.com/docker/docker/client/network_remove.go +++ b/vendor/github.com/docker/docker/client/network_remove.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // NetworkRemove removes an existent network from the docker host. func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error { diff --git a/vendor/github.com/docker/docker/client/node_inspect.go b/vendor/github.com/docker/docker/client/node_inspect.go index 791d2c006..593b2e9f0 100644 --- a/vendor/github.com/docker/docker/client/node_inspect.go +++ b/vendor/github.com/docker/docker/client/node_inspect.go @@ -1,16 +1,19 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NodeInspectWithRaw returns the node information. func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { + if nodeID == "" { + return swarm.Node{}, nil, objectNotFoundError{object: "node", id: nodeID} + } serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) if err != nil { return swarm.Node{}, nil, wrapResponseError(err, serverResp, "node", nodeID) diff --git a/vendor/github.com/docker/docker/client/node_list.go b/vendor/github.com/docker/docker/client/node_list.go index fed22992c..9883f6fc5 100644 --- a/vendor/github.com/docker/docker/client/node_list.go +++ b/vendor/github.com/docker/docker/client/node_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NodeList returns the list of nodes. diff --git a/vendor/github.com/docker/docker/client/node_remove.go b/vendor/github.com/docker/docker/client/node_remove.go index adbf52feb..e7a750571 100644 --- a/vendor/github.com/docker/docker/client/node_remove.go +++ b/vendor/github.com/docker/docker/client/node_remove.go @@ -1,11 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - - "golang.org/x/net/context" ) // NodeRemove removes a Node. diff --git a/vendor/github.com/docker/docker/client/node_update.go b/vendor/github.com/docker/docker/client/node_update.go index 3ca976028..de32a617f 100644 --- a/vendor/github.com/docker/docker/client/node_update.go +++ b/vendor/github.com/docker/docker/client/node_update.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // NodeUpdate updates a Node. diff --git a/vendor/github.com/docker/docker/client/options.go b/vendor/github.com/docker/docker/client/options.go new file mode 100644 index 000000000..12eb25b18 --- /dev/null +++ b/vendor/github.com/docker/docker/client/options.go @@ -0,0 +1,144 @@ +package client + +import ( + "context" + "net" + "net/http" + "os" + "path/filepath" + + "github.com/docker/go-connections/sockets" + "github.com/docker/go-connections/tlsconfig" + "github.com/pkg/errors" +) + +// FromEnv configures the client with values from environment variables. +// +// Supported environment variables: +// DOCKER_HOST to set the url to the docker server. +// DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest. +// DOCKER_CERT_PATH to load the TLS certificates from. +// DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default. +func FromEnv(c *Client) error { + if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" { + options := tlsconfig.Options{ + CAFile: filepath.Join(dockerCertPath, "ca.pem"), + CertFile: filepath.Join(dockerCertPath, "cert.pem"), + KeyFile: filepath.Join(dockerCertPath, "key.pem"), + InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "", + } + tlsc, err := tlsconfig.Client(options) + if err != nil { + return err + } + + c.client = &http.Client{ + Transport: &http.Transport{TLSClientConfig: tlsc}, + CheckRedirect: CheckRedirect, + } + } + + if host := os.Getenv("DOCKER_HOST"); host != "" { + if err := WithHost(host)(c); err != nil { + return err + } + } + + if version := os.Getenv("DOCKER_API_VERSION"); version != "" { + c.version = version + c.manualOverride = true + } + return nil +} + +// WithDialer applies the dialer.DialContext to the client transport. This can be +// used to set the Timeout and KeepAlive settings of the client. +// Deprecated: use WithDialContext +func WithDialer(dialer *net.Dialer) func(*Client) error { + return WithDialContext(dialer.DialContext) +} + +// WithDialContext applies the dialer to the client transport. This can be +// used to set the Timeout and KeepAlive settings of the client. +func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) func(*Client) error { + return func(c *Client) error { + if transport, ok := c.client.Transport.(*http.Transport); ok { + transport.DialContext = dialContext + return nil + } + return errors.Errorf("cannot apply dialer to transport: %T", c.client.Transport) + } +} + +// WithHost overrides the client host with the specified one. +func WithHost(host string) func(*Client) error { + return func(c *Client) error { + hostURL, err := ParseHostURL(host) + if err != nil { + return err + } + c.host = host + c.proto = hostURL.Scheme + c.addr = hostURL.Host + c.basePath = hostURL.Path + if transport, ok := c.client.Transport.(*http.Transport); ok { + return sockets.ConfigureTransport(transport, c.proto, c.addr) + } + return errors.Errorf("cannot apply host to transport: %T", c.client.Transport) + } +} + +// WithHTTPClient overrides the client http client with the specified one +func WithHTTPClient(client *http.Client) func(*Client) error { + return func(c *Client) error { + if client != nil { + c.client = client + } + return nil + } +} + +// WithHTTPHeaders overrides the client default http headers +func WithHTTPHeaders(headers map[string]string) func(*Client) error { + return func(c *Client) error { + c.customHTTPHeaders = headers + return nil + } +} + +// WithScheme overrides the client scheme with the specified one +func WithScheme(scheme string) func(*Client) error { + return func(c *Client) error { + c.scheme = scheme + return nil + } +} + +// WithTLSClientConfig applies a tls config to the client transport. +func WithTLSClientConfig(cacertPath, certPath, keyPath string) func(*Client) error { + return func(c *Client) error { + opts := tlsconfig.Options{ + CAFile: cacertPath, + CertFile: certPath, + KeyFile: keyPath, + ExclusiveRootPools: true, + } + config, err := tlsconfig.Client(opts) + if err != nil { + return errors.Wrap(err, "failed to create tls config") + } + if transport, ok := c.client.Transport.(*http.Transport); ok { + transport.TLSClientConfig = config + return nil + } + return errors.Errorf("cannot apply tls config to transport: %T", c.client.Transport) + } +} + +// WithVersion overrides the client version with the specified one +func WithVersion(version string) func(*Client) error { + return func(c *Client) error { + c.version = version + return nil + } +} diff --git a/vendor/github.com/docker/docker/client/ping.go b/vendor/github.com/docker/docker/client/ping.go index 0b6e450da..0ebb6b752 100644 --- a/vendor/github.com/docker/docker/client/ping.go +++ b/vendor/github.com/docker/docker/client/ping.go @@ -1,32 +1,57 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "net/http" "path" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) -// Ping pings the server and returns the value of the "Docker-Experimental", "OS-Type" & "API-Version" headers +// Ping pings the server and returns the value of the "Docker-Experimental", +// "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use +// a HEAD request on the endpoint, but falls back to GET if HEAD is not supported +// by the daemon. func (cli *Client) Ping(ctx context.Context) (types.Ping, error) { var ping types.Ping - req, err := cli.buildRequest("GET", path.Join(cli.basePath, "/_ping"), nil, nil) + req, err := cli.buildRequest("HEAD", path.Join(cli.basePath, "/_ping"), nil, nil) if err != nil { return ping, err } serverResp, err := cli.doRequest(ctx, req) + if err == nil { + defer ensureReaderClosed(serverResp) + switch serverResp.statusCode { + case http.StatusOK, http.StatusInternalServerError: + // Server handled the request, so parse the response + return parsePingResponse(cli, serverResp) + } + } + + req, err = cli.buildRequest("GET", path.Join(cli.basePath, "/_ping"), nil, nil) + if err != nil { + return ping, err + } + serverResp, err = cli.doRequest(ctx, req) if err != nil { return ping, err } defer ensureReaderClosed(serverResp) + return parsePingResponse(cli, serverResp) +} - if serverResp.header != nil { - ping.APIVersion = serverResp.header.Get("API-Version") - - if serverResp.header.Get("Docker-Experimental") == "true" { - ping.Experimental = true - } - ping.OSType = serverResp.header.Get("OSType") +func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) { + var ping types.Ping + if resp.header == nil { + return ping, cli.checkResponseErr(resp) + } + ping.APIVersion = resp.header.Get("API-Version") + ping.OSType = resp.header.Get("OSType") + if resp.header.Get("Docker-Experimental") == "true" { + ping.Experimental = true + } + if bv := resp.header.Get("Builder-Version"); bv != "" { + ping.BuilderVersion = types.BuilderVersion(bv) } - return ping, cli.checkResponseErr(serverResp) + return ping, cli.checkResponseErr(resp) } diff --git a/vendor/github.com/docker/docker/client/plugin_create.go b/vendor/github.com/docker/docker/client/plugin_create.go index 27954aa57..4591db50f 100644 --- a/vendor/github.com/docker/docker/client/plugin_create.go +++ b/vendor/github.com/docker/docker/client/plugin_create.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/http" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // PluginCreate creates a plugin diff --git a/vendor/github.com/docker/docker/client/plugin_disable.go b/vendor/github.com/docker/docker/client/plugin_disable.go index 30467db74..01f6574f9 100644 --- a/vendor/github.com/docker/docker/client/plugin_disable.go +++ b/vendor/github.com/docker/docker/client/plugin_disable.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // PluginDisable disables a plugin diff --git a/vendor/github.com/docker/docker/client/plugin_enable.go b/vendor/github.com/docker/docker/client/plugin_enable.go index 95517c4b8..736da48bd 100644 --- a/vendor/github.com/docker/docker/client/plugin_enable.go +++ b/vendor/github.com/docker/docker/client/plugin_enable.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "strconv" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // PluginEnable enables a plugin diff --git a/vendor/github.com/docker/docker/client/plugin_inspect.go b/vendor/github.com/docker/docker/client/plugin_inspect.go index 6a6fc18df..0ab7beaee 100644 --- a/vendor/github.com/docker/docker/client/plugin_inspect.go +++ b/vendor/github.com/docker/docker/client/plugin_inspect.go @@ -1,16 +1,19 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // PluginInspectWithRaw inspects an existing plugin func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { + if name == "" { + return nil, nil, objectNotFoundError{object: "plugin", id: name} + } resp, err := cli.get(ctx, "/plugins/"+name+"/json", nil, nil) if err != nil { return nil, nil, wrapResponseError(err, resp, "plugin", name) diff --git a/vendor/github.com/docker/docker/client/plugin_install.go b/vendor/github.com/docker/docker/client/plugin_install.go index ce3e0506e..13baa40a9 100644 --- a/vendor/github.com/docker/docker/client/plugin_install.go +++ b/vendor/github.com/docker/docker/client/plugin_install.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "io" "net/http" @@ -9,7 +10,6 @@ import ( "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/pkg/errors" - "golang.org/x/net/context" ) // PluginInstall installs a plugin diff --git a/vendor/github.com/docker/docker/client/plugin_list.go b/vendor/github.com/docker/docker/client/plugin_list.go index 78dbeb8be..ade1051a9 100644 --- a/vendor/github.com/docker/docker/client/plugin_list.go +++ b/vendor/github.com/docker/docker/client/plugin_list.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // PluginList returns the installed plugins diff --git a/vendor/github.com/docker/docker/client/plugin_push.go b/vendor/github.com/docker/docker/client/plugin_push.go index 1e5f96325..d20bfe844 100644 --- a/vendor/github.com/docker/docker/client/plugin_push.go +++ b/vendor/github.com/docker/docker/client/plugin_push.go @@ -1,9 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" - - "golang.org/x/net/context" ) // PluginPush pushes a plugin to a registry diff --git a/vendor/github.com/docker/docker/client/plugin_remove.go b/vendor/github.com/docker/docker/client/plugin_remove.go index b498c4820..8563bab0d 100644 --- a/vendor/github.com/docker/docker/client/plugin_remove.go +++ b/vendor/github.com/docker/docker/client/plugin_remove.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // PluginRemove removes a plugin diff --git a/vendor/github.com/docker/docker/client/plugin_set.go b/vendor/github.com/docker/docker/client/plugin_set.go index 3260d2a90..dcf5752ca 100644 --- a/vendor/github.com/docker/docker/client/plugin_set.go +++ b/vendor/github.com/docker/docker/client/plugin_set.go @@ -1,7 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( - "golang.org/x/net/context" + "context" ) // PluginSet modifies settings for an existing plugin diff --git a/vendor/github.com/docker/docker/client/plugin_upgrade.go b/vendor/github.com/docker/docker/client/plugin_upgrade.go index 049ebfa2a..115cea945 100644 --- a/vendor/github.com/docker/docker/client/plugin_upgrade.go +++ b/vendor/github.com/docker/docker/client/plugin_upgrade.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/pkg/errors" - "golang.org/x/net/context" ) // PluginUpgrade upgrades a plugin diff --git a/vendor/github.com/docker/docker/client/request.go b/vendor/github.com/docker/docker/client/request.go index 615d0b989..52ed12446 100644 --- a/vendor/github.com/docker/docker/client/request.go +++ b/vendor/github.com/docker/docker/client/request.go @@ -1,7 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -15,8 +16,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/pkg/errors" - "golang.org/x/net/context" - "golang.org/x/net/context/ctxhttp" ) // serverResponse is a wrapper for http API responses. @@ -123,23 +122,21 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u if err != nil { return resp, err } - if err := cli.checkResponseErr(resp); err != nil { - return resp, err - } - return resp, nil + return resp, cli.checkResponseErr(resp) } func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) { serverResp := serverResponse{statusCode: -1, reqURL: req.URL} - resp, err := ctxhttp.Do(ctx, cli.client, req) + req = req.WithContext(ctx) + resp, err := cli.client.Do(req) if err != nil { if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") { return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err) } if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") { - return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err) + return serverResp, errors.Wrap(err, "The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings") } // Don't decorate context sentinel errors; users may be comparing to @@ -198,9 +195,21 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error { return nil } - body, err := ioutil.ReadAll(serverResp.body) - if err != nil { - return err + var body []byte + var err error + if serverResp.body != nil { + bodyMax := 1 * 1024 * 1024 // 1 MiB + bodyR := &io.LimitedReader{ + R: serverResp.body, + N: int64(bodyMax), + } + body, err = ioutil.ReadAll(bodyR) + if err != nil { + return err + } + if bodyR.N == 0 { + return fmt.Errorf("request returned %s with a message (> %d bytes) for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), bodyMax, serverResp.reqURL) + } } if len(body) == 0 { return fmt.Errorf("request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), serverResp.reqURL) @@ -215,14 +224,14 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error { if (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) && ct == "application/json" { var errorResponse types.ErrorResponse if err := json.Unmarshal(body, &errorResponse); err != nil { - return fmt.Errorf("Error reading JSON: %v", err) + return errors.Wrap(err, "Error reading JSON") } - errorMessage = errorResponse.Message + errorMessage = strings.TrimSpace(errorResponse.Message) } else { - errorMessage = string(body) + errorMessage = strings.TrimSpace(string(body)) } - return fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage)) + return errors.Wrap(errors.New(errorMessage), "Error response from daemon") } func (cli *Client) addHeaders(req *http.Request, headers headers) *http.Request { diff --git a/vendor/github.com/docker/docker/client/secret_create.go b/vendor/github.com/docker/docker/client/secret_create.go index 4354afea6..09fae82f2 100644 --- a/vendor/github.com/docker/docker/client/secret_create.go +++ b/vendor/github.com/docker/docker/client/secret_create.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SecretCreate creates a new Secret. diff --git a/vendor/github.com/docker/docker/client/secret_inspect.go b/vendor/github.com/docker/docker/client/secret_inspect.go index 6927ea96f..e8322f458 100644 --- a/vendor/github.com/docker/docker/client/secret_inspect.go +++ b/vendor/github.com/docker/docker/client/secret_inspect.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SecretInspectWithRaw returns the secret information with raw data @@ -14,6 +14,9 @@ func (cli *Client) SecretInspectWithRaw(ctx context.Context, id string) (swarm.S if err := cli.NewVersionError("1.25", "secret inspect"); err != nil { return swarm.Secret{}, nil, err } + if id == "" { + return swarm.Secret{}, nil, objectNotFoundError{object: "secret", id: id} + } resp, err := cli.get(ctx, "/secrets/"+id, nil, nil) if err != nil { return swarm.Secret{}, nil, wrapResponseError(err, resp, "secret", id) diff --git a/vendor/github.com/docker/docker/client/secret_list.go b/vendor/github.com/docker/docker/client/secret_list.go index fdee6e2e0..f6bf7ba47 100644 --- a/vendor/github.com/docker/docker/client/secret_list.go +++ b/vendor/github.com/docker/docker/client/secret_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SecretList returns the list of secrets. diff --git a/vendor/github.com/docker/docker/client/secret_remove.go b/vendor/github.com/docker/docker/client/secret_remove.go index 9b4ee71e2..e9d521829 100644 --- a/vendor/github.com/docker/docker/client/secret_remove.go +++ b/vendor/github.com/docker/docker/client/secret_remove.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // SecretRemove removes a Secret. func (cli *Client) SecretRemove(ctx context.Context, id string) error { diff --git a/vendor/github.com/docker/docker/client/secret_update.go b/vendor/github.com/docker/docker/client/secret_update.go index 875a4c901..164256bbc 100644 --- a/vendor/github.com/docker/docker/client/secret_update.go +++ b/vendor/github.com/docker/docker/client/secret_update.go @@ -1,11 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SecretUpdate attempts to update a Secret diff --git a/vendor/github.com/docker/docker/client/service_create.go b/vendor/github.com/docker/docker/client/service_create.go index 834709d1f..8fadda4a9 100644 --- a/vendor/github.com/docker/docker/client/service_create.go +++ b/vendor/github.com/docker/docker/client/service_create.go @@ -1,6 +1,7 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "strings" @@ -8,9 +9,8 @@ import ( "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/go-digest" "github.com/pkg/errors" - "golang.org/x/net/context" ) // ServiceCreate creates a new Service. @@ -136,7 +136,7 @@ func imageWithDigestString(image string, dgst digest.Digest) string { // imageWithTagString takes an image string, and returns a tagged image // string, adding a 'latest' tag if one was not provided. It returns an -// emptry string if a canonical reference was provided +// empty string if a canonical reference was provided func imageWithTagString(image string) string { namedRef, err := reference.ParseNormalizedNamed(image) if err == nil { diff --git a/vendor/github.com/docker/docker/client/service_inspect.go b/vendor/github.com/docker/docker/client/service_inspect.go index 3e9699e5e..de6aa22de 100644 --- a/vendor/github.com/docker/docker/client/service_inspect.go +++ b/vendor/github.com/docker/docker/client/service_inspect.go @@ -1,7 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "fmt" "io/ioutil" @@ -9,11 +10,13 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ServiceInspectWithRaw returns the service information and the raw data. func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string, opts types.ServiceInspectOptions) (swarm.Service, []byte, error) { + if serviceID == "" { + return swarm.Service{}, nil, objectNotFoundError{object: "service", id: serviceID} + } query := url.Values{} query.Set("insertDefaults", fmt.Sprintf("%v", opts.InsertDefaults)) serverResp, err := cli.get(ctx, "/services/"+serviceID, query, nil) diff --git a/vendor/github.com/docker/docker/client/service_list.go b/vendor/github.com/docker/docker/client/service_list.go index eb3ff9739..7d53e2b9b 100644 --- a/vendor/github.com/docker/docker/client/service_list.go +++ b/vendor/github.com/docker/docker/client/service_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // ServiceList returns the list of services. diff --git a/vendor/github.com/docker/docker/client/service_logs.go b/vendor/github.com/docker/docker/client/service_logs.go index 24384e3ec..906fd4059 100644 --- a/vendor/github.com/docker/docker/client/service_logs.go +++ b/vendor/github.com/docker/docker/client/service_logs.go @@ -1,14 +1,14 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "time" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" timetypes "github.com/docker/docker/api/types/time" + "github.com/pkg/errors" ) // ServiceLogs returns the logs generated by a service in an io.ReadCloser. @@ -26,7 +26,7 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ty if options.Since != "" { ts, err := timetypes.GetTimestamp(options.Since, time.Now()) if err != nil { - return nil, err + return nil, errors.Wrap(err, `invalid value for "since"`) } query.Set("since", ts) } diff --git a/vendor/github.com/docker/docker/client/service_remove.go b/vendor/github.com/docker/docker/client/service_remove.go index ad992c01d..fe3421bec 100644 --- a/vendor/github.com/docker/docker/client/service_remove.go +++ b/vendor/github.com/docker/docker/client/service_remove.go @@ -1,6 +1,6 @@ -package client +package client // import "github.com/docker/docker/client" -import "golang.org/x/net/context" +import "context" // ServiceRemove kills and removes a service. func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { diff --git a/vendor/github.com/docker/docker/client/service_update.go b/vendor/github.com/docker/docker/client/service_update.go index 8764f299a..3c21214f6 100644 --- a/vendor/github.com/docker/docker/client/service_update.go +++ b/vendor/github.com/docker/docker/client/service_update.go @@ -1,16 +1,18 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "strconv" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) -// ServiceUpdate updates a Service. +// ServiceUpdate updates a Service. The version number is required to avoid conflicting writes. +// It should be the value as set *before* the update. You can find this value in the Meta field +// of swarm.Service, which can be found using ServiceInspectWithRaw. func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { var ( query = url.Values{} diff --git a/vendor/github.com/docker/docker/client/session.go b/vendor/github.com/docker/docker/client/session.go index 8ee916213..df199f3d0 100644 --- a/vendor/github.com/docker/docker/client/session.go +++ b/vendor/github.com/docker/docker/client/session.go @@ -1,10 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net" "net/http" - - "golang.org/x/net/context" ) // DialSession returns a connection that can be used communication with daemon @@ -15,5 +14,5 @@ func (cli *Client) DialSession(ctx context.Context, proto string, meta map[strin } req = cli.addHeaders(req, meta) - return cli.setupHijackConn(req, proto) + return cli.setupHijackConn(ctx, req, proto) } diff --git a/vendor/github.com/docker/docker/client/swarm_get_unlock_key.go b/vendor/github.com/docker/docker/client/swarm_get_unlock_key.go index be28d3262..0c50c01a8 100644 --- a/vendor/github.com/docker/docker/client/swarm_get_unlock_key.go +++ b/vendor/github.com/docker/docker/client/swarm_get_unlock_key.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // SwarmGetUnlockKey retrieves the swarm's unlock key. diff --git a/vendor/github.com/docker/docker/client/swarm_init.go b/vendor/github.com/docker/docker/client/swarm_init.go index 9e65e1cca..742ca0f04 100644 --- a/vendor/github.com/docker/docker/client/swarm_init.go +++ b/vendor/github.com/docker/docker/client/swarm_init.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SwarmInit initializes the swarm. diff --git a/vendor/github.com/docker/docker/client/swarm_inspect.go b/vendor/github.com/docker/docker/client/swarm_inspect.go index 77e72f846..cfaabb25b 100644 --- a/vendor/github.com/docker/docker/client/swarm_inspect.go +++ b/vendor/github.com/docker/docker/client/swarm_inspect.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SwarmInspect inspects the swarm. diff --git a/vendor/github.com/docker/docker/client/swarm_join.go b/vendor/github.com/docker/docker/client/swarm_join.go index 19e5192b9..a1cf0455d 100644 --- a/vendor/github.com/docker/docker/client/swarm_join.go +++ b/vendor/github.com/docker/docker/client/swarm_join.go @@ -1,8 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SwarmJoin joins the swarm. diff --git a/vendor/github.com/docker/docker/client/swarm_leave.go b/vendor/github.com/docker/docker/client/swarm_leave.go index 3a205cf3b..90ca84b36 100644 --- a/vendor/github.com/docker/docker/client/swarm_leave.go +++ b/vendor/github.com/docker/docker/client/swarm_leave.go @@ -1,9 +1,8 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" - - "golang.org/x/net/context" ) // SwarmLeave leaves the swarm. diff --git a/vendor/github.com/docker/docker/client/swarm_unlock.go b/vendor/github.com/docker/docker/client/swarm_unlock.go index 9ee441fed..d2412f7d4 100644 --- a/vendor/github.com/docker/docker/client/swarm_unlock.go +++ b/vendor/github.com/docker/docker/client/swarm_unlock.go @@ -1,8 +1,9 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" + "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SwarmUnlock unlocks locked swarm. diff --git a/vendor/github.com/docker/docker/client/swarm_update.go b/vendor/github.com/docker/docker/client/swarm_update.go index 7245fd4e3..56a5bea76 100644 --- a/vendor/github.com/docker/docker/client/swarm_update.go +++ b/vendor/github.com/docker/docker/client/swarm_update.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "fmt" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // SwarmUpdate updates the swarm. diff --git a/vendor/github.com/docker/docker/client/task_inspect.go b/vendor/github.com/docker/docker/client/task_inspect.go index dc08cedb9..e1c0a736d 100644 --- a/vendor/github.com/docker/docker/client/task_inspect.go +++ b/vendor/github.com/docker/docker/client/task_inspect.go @@ -1,16 +1,19 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // TaskInspectWithRaw returns the task information and its raw representation.. func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { + if taskID == "" { + return swarm.Task{}, nil, objectNotFoundError{object: "task", id: taskID} + } serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) if err != nil { return swarm.Task{}, nil, wrapResponseError(err, serverResp, "task", taskID) diff --git a/vendor/github.com/docker/docker/client/task_list.go b/vendor/github.com/docker/docker/client/task_list.go index 01bd69525..42d20c1b8 100644 --- a/vendor/github.com/docker/docker/client/task_list.go +++ b/vendor/github.com/docker/docker/client/task_list.go @@ -1,13 +1,13 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/swarm" - "golang.org/x/net/context" ) // TaskList returns the list of tasks. diff --git a/vendor/github.com/docker/docker/client/task_logs.go b/vendor/github.com/docker/docker/client/task_logs.go index 2ed19543a..6222fab57 100644 --- a/vendor/github.com/docker/docker/client/task_logs.go +++ b/vendor/github.com/docker/docker/client/task_logs.go @@ -1,12 +1,11 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "io" "net/url" "time" - "golang.org/x/net/context" - "github.com/docker/docker/api/types" timetypes "github.com/docker/docker/api/types/time" ) diff --git a/vendor/github.com/docker/docker/client/tlsconfig_clone.go b/vendor/github.com/docker/docker/client/tlsconfig_clone.go deleted file mode 100644 index 99b6be1ce..000000000 --- a/vendor/github.com/docker/docker/client/tlsconfig_clone.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build go1.8 - -package client - -import "crypto/tls" - -// tlsConfigClone returns a clone of tls.Config. This function is provided for -// compatibility for go1.7 that doesn't include this method in stdlib. -func tlsConfigClone(c *tls.Config) *tls.Config { - return c.Clone() -} diff --git a/vendor/github.com/docker/docker/client/tlsconfig_clone_go17.go b/vendor/github.com/docker/docker/client/tlsconfig_clone_go17.go deleted file mode 100644 index b837b2ade..000000000 --- a/vendor/github.com/docker/docker/client/tlsconfig_clone_go17.go +++ /dev/null @@ -1,33 +0,0 @@ -// +build go1.7,!go1.8 - -package client - -import "crypto/tls" - -// tlsConfigClone returns a clone of tls.Config. This function is provided for -// compatibility for go1.7 that doesn't include this method in stdlib. -func tlsConfigClone(c *tls.Config) *tls.Config { - return &tls.Config{ - Rand: c.Rand, - Time: c.Time, - Certificates: c.Certificates, - NameToCertificate: c.NameToCertificate, - GetCertificate: c.GetCertificate, - RootCAs: c.RootCAs, - NextProtos: c.NextProtos, - ServerName: c.ServerName, - ClientAuth: c.ClientAuth, - ClientCAs: c.ClientCAs, - InsecureSkipVerify: c.InsecureSkipVerify, - CipherSuites: c.CipherSuites, - PreferServerCipherSuites: c.PreferServerCipherSuites, - SessionTicketsDisabled: c.SessionTicketsDisabled, - SessionTicketKey: c.SessionTicketKey, - ClientSessionCache: c.ClientSessionCache, - MinVersion: c.MinVersion, - MaxVersion: c.MaxVersion, - CurvePreferences: c.CurvePreferences, - DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, - Renegotiation: c.Renegotiation, - } -} diff --git a/vendor/github.com/docker/docker/client/transport.go b/vendor/github.com/docker/docker/client/transport.go index 73f6ef7b4..554134436 100644 --- a/vendor/github.com/docker/docker/client/transport.go +++ b/vendor/github.com/docker/docker/client/transport.go @@ -1,4 +1,4 @@ -package client +package client // import "github.com/docker/docker/client" import ( "crypto/tls" diff --git a/vendor/github.com/docker/docker/client/utils.go b/vendor/github.com/docker/docker/client/utils.go index 137705065..7f3ff44eb 100644 --- a/vendor/github.com/docker/docker/client/utils.go +++ b/vendor/github.com/docker/docker/client/utils.go @@ -1,4 +1,4 @@ -package client +package client // import "github.com/docker/docker/client" import ( "net/url" diff --git a/vendor/github.com/docker/docker/client/version.go b/vendor/github.com/docker/docker/client/version.go index 933ceb4a4..1989f6d6d 100644 --- a/vendor/github.com/docker/docker/client/version.go +++ b/vendor/github.com/docker/docker/client/version.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // ServerVersion returns information of the docker client and server host. diff --git a/vendor/github.com/docker/docker/client/volume_create.go b/vendor/github.com/docker/docker/client/volume_create.go index 9620c87cb..f1f6fcdc4 100644 --- a/vendor/github.com/docker/docker/client/volume_create.go +++ b/vendor/github.com/docker/docker/client/volume_create.go @@ -1,15 +1,15 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" - "golang.org/x/net/context" ) // VolumeCreate creates a volume in the docker host. -func (cli *Client) VolumeCreate(ctx context.Context, options volumetypes.VolumesCreateBody) (types.Volume, error) { +func (cli *Client) VolumeCreate(ctx context.Context, options volumetypes.VolumeCreateBody) (types.Volume, error) { var volume types.Volume resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) if err != nil { diff --git a/vendor/github.com/docker/docker/client/volume_inspect.go b/vendor/github.com/docker/docker/client/volume_inspect.go index 988934384..f840682d2 100644 --- a/vendor/github.com/docker/docker/client/volume_inspect.go +++ b/vendor/github.com/docker/docker/client/volume_inspect.go @@ -1,13 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( "bytes" + "context" "encoding/json" "io/ioutil" - "path" "github.com/docker/docker/api/types" - "golang.org/x/net/context" ) // VolumeInspect returns the information about a specific volume in the docker host. @@ -18,15 +17,12 @@ func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Vo // VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) { - // The empty ID needs to be handled here because with an empty ID the - // request url will not contain a trailing / which calls the volume list API - // instead of volume inspect if volumeID == "" { return types.Volume{}, nil, objectNotFoundError{object: "volume", id: volumeID} } var volume types.Volume - resp, err := cli.get(ctx, path.Join("/volumes", volumeID), nil, nil) + resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) if err != nil { return volume, nil, wrapResponseError(err, resp, "volume", volumeID) } diff --git a/vendor/github.com/docker/docker/client/volume_list.go b/vendor/github.com/docker/docker/client/volume_list.go index 32247ce11..284554d67 100644 --- a/vendor/github.com/docker/docker/client/volume_list.go +++ b/vendor/github.com/docker/docker/client/volume_list.go @@ -1,17 +1,17 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "net/url" "github.com/docker/docker/api/types/filters" volumetypes "github.com/docker/docker/api/types/volume" - "golang.org/x/net/context" ) // VolumeList returns the volumes configured in the docker host. -func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumesListOKBody, error) { - var volumes volumetypes.VolumesListOKBody +func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (volumetypes.VolumeListOKBody, error) { + var volumes volumetypes.VolumeListOKBody query := url.Values{} if filter.Len() > 0 { diff --git a/vendor/github.com/docker/docker/client/volume_prune.go b/vendor/github.com/docker/docker/client/volume_prune.go index 2e7fea774..70041efed 100644 --- a/vendor/github.com/docker/docker/client/volume_prune.go +++ b/vendor/github.com/docker/docker/client/volume_prune.go @@ -1,12 +1,12 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "encoding/json" "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "golang.org/x/net/context" ) // VolumesPrune requests the daemon to delete unused data diff --git a/vendor/github.com/docker/docker/client/volume_remove.go b/vendor/github.com/docker/docker/client/volume_remove.go index 3ffb8bcf2..fc5a71d33 100644 --- a/vendor/github.com/docker/docker/client/volume_remove.go +++ b/vendor/github.com/docker/docker/client/volume_remove.go @@ -1,10 +1,10 @@ -package client +package client // import "github.com/docker/docker/client" import ( + "context" "net/url" "github.com/docker/docker/api/types/versions" - "golang.org/x/net/context" ) // VolumeRemove removes a volume from the docker host. diff --git a/vendor/github.com/docker/docker/daemon/caps/utils_unix.go b/vendor/github.com/docker/docker/daemon/caps/utils_unix.go deleted file mode 100644 index c99485f51..000000000 --- a/vendor/github.com/docker/docker/daemon/caps/utils_unix.go +++ /dev/null @@ -1,131 +0,0 @@ -// +build !windows - -package caps - -import ( - "fmt" - "strings" - - "github.com/docker/docker/pkg/stringutils" - "github.com/syndtr/gocapability/capability" -) - -var capabilityList Capabilities - -func init() { - last := capability.CAP_LAST_CAP - // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap - if last == capability.Cap(63) { - last = capability.CAP_BLOCK_SUSPEND - } - for _, cap := range capability.List() { - if cap > last { - continue - } - capabilityList = append(capabilityList, - &CapabilityMapping{ - Key: "CAP_" + strings.ToUpper(cap.String()), - Value: cap, - }, - ) - } -} - -type ( - // CapabilityMapping maps linux capability name to its value of capability.Cap type - // Capabilities is one of the security systems in Linux Security Module (LSM) - // framework provided by the kernel. - // For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html - CapabilityMapping struct { - Key string `json:"key,omitempty"` - Value capability.Cap `json:"value,omitempty"` - } - // Capabilities contains all CapabilityMapping - Capabilities []*CapabilityMapping -) - -// String returns <key> of CapabilityMapping -func (c *CapabilityMapping) String() string { - return c.Key -} - -// GetCapability returns CapabilityMapping which contains specific key -func GetCapability(key string) *CapabilityMapping { - for _, capp := range capabilityList { - if capp.Key == key { - cpy := *capp - return &cpy - } - } - return nil -} - -// GetAllCapabilities returns all of the capabilities -func GetAllCapabilities() []string { - output := make([]string, len(capabilityList)) - for i, capability := range capabilityList { - output[i] = capability.String() - } - return output -} - -// TweakCapabilities can tweak capabilities by adding or dropping capabilities -// based on the basics capabilities. -func TweakCapabilities(basics, adds, drops []string) ([]string, error) { - var ( - newCaps []string - allCaps = GetAllCapabilities() - ) - - // FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix - // Currently they are mixed in here. We should do conversion in one place. - - // look for invalid cap in the drop list - for _, cap := range drops { - if strings.ToLower(cap) == "all" { - continue - } - - if !stringutils.InSlice(allCaps, "CAP_"+cap) { - return nil, fmt.Errorf("Unknown capability drop: %q", cap) - } - } - - // handle --cap-add=all - if stringutils.InSlice(adds, "all") { - basics = allCaps - } - - if !stringutils.InSlice(drops, "all") { - for _, cap := range basics { - // skip `all` already handled above - if strings.ToLower(cap) == "all" { - continue - } - - // if we don't drop `all`, add back all the non-dropped caps - if !stringutils.InSlice(drops, cap[4:]) { - newCaps = append(newCaps, strings.ToUpper(cap)) - } - } - } - - for _, cap := range adds { - // skip `all` already handled above - if strings.ToLower(cap) == "all" { - continue - } - - cap = "CAP_" + cap - - if !stringutils.InSlice(allCaps, cap) { - return nil, fmt.Errorf("Unknown capability to add: %q", cap) - } - - // add cap if not already in the list - if !stringutils.InSlice(newCaps, cap) { - newCaps = append(newCaps, strings.ToUpper(cap)) - } - } - return newCaps, nil -} diff --git a/vendor/github.com/docker/docker/errdefs/defs.go b/vendor/github.com/docker/docker/errdefs/defs.go new file mode 100644 index 000000000..e6a2275b2 --- /dev/null +++ b/vendor/github.com/docker/docker/errdefs/defs.go @@ -0,0 +1,74 @@ +package errdefs // import "github.com/docker/docker/errdefs" + +// ErrNotFound signals that the requested object doesn't exist +type ErrNotFound interface { + NotFound() +} + +// ErrInvalidParameter signals that the user input is invalid +type ErrInvalidParameter interface { + InvalidParameter() +} + +// ErrConflict signals that some internal state conflicts with the requested action and can't be performed. +// A change in state should be able to clear this error. +type ErrConflict interface { + Conflict() +} + +// ErrUnauthorized is used to signify that the user is not authorized to perform a specific action +type ErrUnauthorized interface { + Unauthorized() +} + +// ErrUnavailable signals that the requested action/subsystem is not available. +type ErrUnavailable interface { + Unavailable() +} + +// ErrForbidden signals that the requested action cannot be performed under any circumstances. +// When a ErrForbidden is returned, the caller should never retry the action. +type ErrForbidden interface { + Forbidden() +} + +// ErrSystem signals that some internal error occurred. +// An example of this would be a failed mount request. +type ErrSystem interface { + System() +} + +// ErrNotModified signals that an action can't be performed because it's already in the desired state +type ErrNotModified interface { + NotModified() +} + +// ErrAlreadyExists is a special case of ErrConflict which signals that the desired object already exists +type ErrAlreadyExists interface { + AlreadyExists() +} + +// ErrNotImplemented signals that the requested action/feature is not implemented on the system as configured. +type ErrNotImplemented interface { + NotImplemented() +} + +// ErrUnknown signals that the kind of error that occurred is not known. +type ErrUnknown interface { + Unknown() +} + +// ErrCancelled signals that the action was cancelled. +type ErrCancelled interface { + Cancelled() +} + +// ErrDeadline signals that the deadline was reached before the action completed. +type ErrDeadline interface { + DeadlineExceeded() +} + +// ErrDataLoss indicates that data was lost or there is data corruption. +type ErrDataLoss interface { + DataLoss() +} diff --git a/vendor/github.com/docker/docker/errdefs/doc.go b/vendor/github.com/docker/docker/errdefs/doc.go new file mode 100644 index 000000000..c211f174f --- /dev/null +++ b/vendor/github.com/docker/docker/errdefs/doc.go @@ -0,0 +1,8 @@ +// Package errdefs defines a set of error interfaces that packages should use for communicating classes of errors. +// Errors that cross the package boundary should implement one (and only one) of these interfaces. +// +// Packages should not reference these interfaces directly, only implement them. +// To check if a particular error implements one of these interfaces, there are helper +// functions provided (e.g. `Is<SomeError>`) which can be used rather than asserting the interfaces directly. +// If you must assert on these interfaces, be sure to check the causal chain (`err.Cause()`). +package errdefs // import "github.com/docker/docker/errdefs" diff --git a/vendor/github.com/docker/docker/errdefs/helpers.go b/vendor/github.com/docker/docker/errdefs/helpers.go new file mode 100644 index 000000000..a28881caf --- /dev/null +++ b/vendor/github.com/docker/docker/errdefs/helpers.go @@ -0,0 +1,243 @@ +package errdefs // import "github.com/docker/docker/errdefs" + +import "context" + +type errNotFound struct{ error } + +func (errNotFound) NotFound() {} + +func (e errNotFound) Cause() error { + return e.error +} + +// NotFound is a helper to create an error of the class with the same name from any error type +func NotFound(err error) error { + if err == nil || IsNotFound(err) { + return err + } + return errNotFound{err} +} + +type errInvalidParameter struct{ error } + +func (errInvalidParameter) InvalidParameter() {} + +func (e errInvalidParameter) Cause() error { + return e.error +} + +// InvalidParameter is a helper to create an error of the class with the same name from any error type +func InvalidParameter(err error) error { + if err == nil || IsInvalidParameter(err) { + return err + } + return errInvalidParameter{err} +} + +type errConflict struct{ error } + +func (errConflict) Conflict() {} + +func (e errConflict) Cause() error { + return e.error +} + +// Conflict is a helper to create an error of the class with the same name from any error type +func Conflict(err error) error { + if err == nil || IsConflict(err) { + return err + } + return errConflict{err} +} + +type errUnauthorized struct{ error } + +func (errUnauthorized) Unauthorized() {} + +func (e errUnauthorized) Cause() error { + return e.error +} + +// Unauthorized is a helper to create an error of the class with the same name from any error type +func Unauthorized(err error) error { + if err == nil || IsUnauthorized(err) { + return err + } + return errUnauthorized{err} +} + +type errUnavailable struct{ error } + +func (errUnavailable) Unavailable() {} + +func (e errUnavailable) Cause() error { + return e.error +} + +// Unavailable is a helper to create an error of the class with the same name from any error type +func Unavailable(err error) error { + if err == nil || IsUnavailable(err) { + return err + } + return errUnavailable{err} +} + +type errForbidden struct{ error } + +func (errForbidden) Forbidden() {} + +func (e errForbidden) Cause() error { + return e.error +} + +// Forbidden is a helper to create an error of the class with the same name from any error type +func Forbidden(err error) error { + if err == nil || IsForbidden(err) { + return err + } + return errForbidden{err} +} + +type errSystem struct{ error } + +func (errSystem) System() {} + +func (e errSystem) Cause() error { + return e.error +} + +// System is a helper to create an error of the class with the same name from any error type +func System(err error) error { + if err == nil || IsSystem(err) { + return err + } + return errSystem{err} +} + +type errNotModified struct{ error } + +func (errNotModified) NotModified() {} + +func (e errNotModified) Cause() error { + return e.error +} + +// NotModified is a helper to create an error of the class with the same name from any error type +func NotModified(err error) error { + if err == nil || IsNotModified(err) { + return err + } + return errNotModified{err} +} + +type errAlreadyExists struct{ error } + +func (errAlreadyExists) AlreadyExists() {} + +func (e errAlreadyExists) Cause() error { + return e.error +} + +// AlreadyExists is a helper to create an error of the class with the same name from any error type +func AlreadyExists(err error) error { + if err == nil || IsAlreadyExists(err) { + return err + } + return errAlreadyExists{err} +} + +type errNotImplemented struct{ error } + +func (errNotImplemented) NotImplemented() {} + +func (e errNotImplemented) Cause() error { + return e.error +} + +// NotImplemented is a helper to create an error of the class with the same name from any error type +func NotImplemented(err error) error { + if err == nil || IsNotImplemented(err) { + return err + } + return errNotImplemented{err} +} + +type errUnknown struct{ error } + +func (errUnknown) Unknown() {} + +func (e errUnknown) Cause() error { + return e.error +} + +// Unknown is a helper to create an error of the class with the same name from any error type +func Unknown(err error) error { + if err == nil || IsUnknown(err) { + return err + } + return errUnknown{err} +} + +type errCancelled struct{ error } + +func (errCancelled) Cancelled() {} + +func (e errCancelled) Cause() error { + return e.error +} + +// Cancelled is a helper to create an error of the class with the same name from any error type +func Cancelled(err error) error { + if err == nil || IsCancelled(err) { + return err + } + return errCancelled{err} +} + +type errDeadline struct{ error } + +func (errDeadline) DeadlineExceeded() {} + +func (e errDeadline) Cause() error { + return e.error +} + +// Deadline is a helper to create an error of the class with the same name from any error type +func Deadline(err error) error { + if err == nil || IsDeadline(err) { + return err + } + return errDeadline{err} +} + +type errDataLoss struct{ error } + +func (errDataLoss) DataLoss() {} + +func (e errDataLoss) Cause() error { + return e.error +} + +// DataLoss is a helper to create an error of the class with the same name from any error type +func DataLoss(err error) error { + if err == nil || IsDataLoss(err) { + return err + } + return errDataLoss{err} +} + +// FromContext returns the error class from the passed in context +func FromContext(ctx context.Context) error { + e := ctx.Err() + if e == nil { + return nil + } + + if e == context.Canceled { + return Cancelled(e) + } + if e == context.DeadlineExceeded { + return Deadline(e) + } + return Unknown(e) +} diff --git a/vendor/github.com/docker/docker/errdefs/is.go b/vendor/github.com/docker/docker/errdefs/is.go new file mode 100644 index 000000000..e0513331b --- /dev/null +++ b/vendor/github.com/docker/docker/errdefs/is.go @@ -0,0 +1,114 @@ +package errdefs // import "github.com/docker/docker/errdefs" + +type causer interface { + Cause() error +} + +func getImplementer(err error) error { + switch e := err.(type) { + case + ErrNotFound, + ErrInvalidParameter, + ErrConflict, + ErrUnauthorized, + ErrUnavailable, + ErrForbidden, + ErrSystem, + ErrNotModified, + ErrAlreadyExists, + ErrNotImplemented, + ErrCancelled, + ErrDeadline, + ErrDataLoss, + ErrUnknown: + return err + case causer: + return getImplementer(e.Cause()) + default: + return err + } +} + +// IsNotFound returns if the passed in error is an ErrNotFound +func IsNotFound(err error) bool { + _, ok := getImplementer(err).(ErrNotFound) + return ok +} + +// IsInvalidParameter returns if the passed in error is an ErrInvalidParameter +func IsInvalidParameter(err error) bool { + _, ok := getImplementer(err).(ErrInvalidParameter) + return ok +} + +// IsConflict returns if the passed in error is an ErrConflict +func IsConflict(err error) bool { + _, ok := getImplementer(err).(ErrConflict) + return ok +} + +// IsUnauthorized returns if the passed in error is an ErrUnauthorized +func IsUnauthorized(err error) bool { + _, ok := getImplementer(err).(ErrUnauthorized) + return ok +} + +// IsUnavailable returns if the passed in error is an ErrUnavailable +func IsUnavailable(err error) bool { + _, ok := getImplementer(err).(ErrUnavailable) + return ok +} + +// IsForbidden returns if the passed in error is an ErrForbidden +func IsForbidden(err error) bool { + _, ok := getImplementer(err).(ErrForbidden) + return ok +} + +// IsSystem returns if the passed in error is an ErrSystem +func IsSystem(err error) bool { + _, ok := getImplementer(err).(ErrSystem) + return ok +} + +// IsNotModified returns if the passed in error is a NotModified error +func IsNotModified(err error) bool { + _, ok := getImplementer(err).(ErrNotModified) + return ok +} + +// IsAlreadyExists returns if the passed in error is a AlreadyExists error +func IsAlreadyExists(err error) bool { + _, ok := getImplementer(err).(ErrAlreadyExists) + return ok +} + +// IsNotImplemented returns if the passed in error is an ErrNotImplemented +func IsNotImplemented(err error) bool { + _, ok := getImplementer(err).(ErrNotImplemented) + return ok +} + +// IsUnknown returns if the passed in error is an ErrUnknown +func IsUnknown(err error) bool { + _, ok := getImplementer(err).(ErrUnknown) + return ok +} + +// IsCancelled returns if the passed in error is an ErrCancelled +func IsCancelled(err error) bool { + _, ok := getImplementer(err).(ErrCancelled) + return ok +} + +// IsDeadline returns if the passed in error is an ErrDeadline +func IsDeadline(err error) bool { + _, ok := getImplementer(err).(ErrDeadline) + return ok +} + +// IsDataLoss returns if the passed in error is an ErrDataLoss +func IsDataLoss(err error) bool { + _, ok := getImplementer(err).(ErrDataLoss) + return ok +} diff --git a/vendor/github.com/docker/docker/oci/caps/utils.go b/vendor/github.com/docker/docker/oci/caps/utils.go new file mode 100644 index 000000000..ffd3f6f50 --- /dev/null +++ b/vendor/github.com/docker/docker/oci/caps/utils.go @@ -0,0 +1,169 @@ +package caps // import "github.com/docker/docker/oci/caps" + +import ( + "fmt" + "strings" + + "github.com/docker/docker/errdefs" + "github.com/syndtr/gocapability/capability" +) + +var capabilityList Capabilities + +func init() { + last := capability.CAP_LAST_CAP + // hack for RHEL6 which has no /proc/sys/kernel/cap_last_cap + if last == capability.Cap(63) { + last = capability.CAP_BLOCK_SUSPEND + } + for _, cap := range capability.List() { + if cap > last { + continue + } + capabilityList = append(capabilityList, + &CapabilityMapping{ + Key: "CAP_" + strings.ToUpper(cap.String()), + Value: cap, + }, + ) + } +} + +type ( + // CapabilityMapping maps linux capability name to its value of capability.Cap type + // Capabilities is one of the security systems in Linux Security Module (LSM) + // framework provided by the kernel. + // For more details on capabilities, see http://man7.org/linux/man-pages/man7/capabilities.7.html + CapabilityMapping struct { + Key string `json:"key,omitempty"` + Value capability.Cap `json:"value,omitempty"` + } + // Capabilities contains all CapabilityMapping + Capabilities []*CapabilityMapping +) + +// String returns <key> of CapabilityMapping +func (c *CapabilityMapping) String() string { + return c.Key +} + +// GetCapability returns CapabilityMapping which contains specific key +func GetCapability(key string) *CapabilityMapping { + for _, capp := range capabilityList { + if capp.Key == key { + cpy := *capp + return &cpy + } + } + return nil +} + +// GetAllCapabilities returns all of the capabilities +func GetAllCapabilities() []string { + output := make([]string, len(capabilityList)) + for i, capability := range capabilityList { + output[i] = capability.String() + } + return output +} + +// inSlice tests whether a string is contained in a slice of strings or not. +func inSlice(slice []string, s string) bool { + for _, ss := range slice { + if s == ss { + return true + } + } + return false +} + +const allCapabilities = "ALL" + +// NormalizeLegacyCapabilities normalizes, and validates CapAdd/CapDrop capabilities +// by upper-casing them, and adding a CAP_ prefix (if not yet present). +// +// This function also accepts the "ALL" magic-value, that's used by CapAdd/CapDrop. +func NormalizeLegacyCapabilities(caps []string) ([]string, error) { + var normalized []string + + valids := GetAllCapabilities() + for _, c := range caps { + c = strings.ToUpper(c) + if c == allCapabilities { + normalized = append(normalized, c) + continue + } + if !strings.HasPrefix(c, "CAP_") { + c = "CAP_" + c + } + if !inSlice(valids, c) { + return nil, errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c)) + } + normalized = append(normalized, c) + } + return normalized, nil +} + +// ValidateCapabilities validates if caps only contains valid capabilities +func ValidateCapabilities(caps []string) error { + valids := GetAllCapabilities() + for _, c := range caps { + if !inSlice(valids, c) { + return errdefs.InvalidParameter(fmt.Errorf("unknown capability: %q", c)) + } + } + return nil +} + +// TweakCapabilities tweaks capabilities by adding, dropping, or overriding +// capabilities in the basics capabilities list. +func TweakCapabilities(basics, adds, drops, capabilities []string, privileged bool) ([]string, error) { + switch { + case privileged: + // Privileged containers get all capabilities + return GetAllCapabilities(), nil + case capabilities != nil: + // Use custom set of capabilities + if err := ValidateCapabilities(capabilities); err != nil { + return nil, err + } + return capabilities, nil + case len(adds) == 0 && len(drops) == 0: + // Nothing to tweak; we're done + return basics, nil + } + + capDrop, err := NormalizeLegacyCapabilities(drops) + if err != nil { + return nil, err + } + capAdd, err := NormalizeLegacyCapabilities(adds) + if err != nil { + return nil, err + } + + var caps []string + + switch { + case inSlice(capAdd, allCapabilities): + // Add all capabilities except ones on capDrop + for _, c := range GetAllCapabilities() { + if !inSlice(capDrop, c) { + caps = append(caps, c) + } + } + case inSlice(capDrop, allCapabilities): + // "Drop" all capabilities; use what's in capAdd instead + caps = capAdd + default: + // First drop some capabilities + for _, c := range basics { + if !inSlice(capDrop, c) { + caps = append(caps, c) + } + } + // Then add the list of capabilities from capAdd + caps = append(caps, capAdd...) + } + return caps, nil +} diff --git a/vendor/github.com/docker/docker/opts/address_pools.go b/vendor/github.com/docker/docker/opts/address_pools.go new file mode 100644 index 000000000..9b27a6285 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/address_pools.go @@ -0,0 +1,84 @@ +package opts + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "strconv" + "strings" + + types "github.com/docker/libnetwork/ipamutils" +) + +// PoolsOpt is a Value type for parsing the default address pools definitions +type PoolsOpt struct { + values []*types.NetworkToSplit +} + +// UnmarshalJSON fills values structure info from JSON input +func (p *PoolsOpt) UnmarshalJSON(raw []byte) error { + return json.Unmarshal(raw, &(p.values)) +} + +// Set predefined pools +func (p *PoolsOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + poolsDef := types.NetworkToSplit{} + + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + key := strings.ToLower(parts[0]) + value := strings.ToLower(parts[1]) + + switch key { + case "base": + poolsDef.Base = value + case "size": + size, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("invalid size value: %q (must be integer): %v", value, err) + } + poolsDef.Size = size + default: + return fmt.Errorf("unexpected key '%s' in '%s'", key, field) + } + } + + p.values = append(p.values, &poolsDef) + + return nil +} + +// Type returns the type of this option +func (p *PoolsOpt) Type() string { + return "pool-options" +} + +// String returns a string repr of this option +func (p *PoolsOpt) String() string { + var pools []string + for _, pool := range p.values { + repr := fmt.Sprintf("%s %d", pool.Base, pool.Size) + pools = append(pools, repr) + } + return strings.Join(pools, ", ") +} + +// Value returns the mounts +func (p *PoolsOpt) Value() []*types.NetworkToSplit { + return p.values +} + +// Name returns the flag name of this option +func (p *PoolsOpt) Name() string { + return "default-address-pools" +} diff --git a/vendor/github.com/docker/docker/opts/env.go b/vendor/github.com/docker/docker/opts/env.go index 4fbd470bc..f6e5e9074 100644 --- a/vendor/github.com/docker/docker/opts/env.go +++ b/vendor/github.com/docker/docker/opts/env.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" diff --git a/vendor/github.com/docker/docker/opts/hosts.go b/vendor/github.com/docker/docker/opts/hosts.go index 594cccf2f..3d8785f11 100644 --- a/vendor/github.com/docker/docker/opts/hosts.go +++ b/vendor/github.com/docker/docker/opts/hosts.go @@ -1,11 +1,14 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" "net" "net/url" + "path/filepath" "strconv" "strings" + + "github.com/docker/docker/pkg/homedir" ) var ( @@ -29,9 +32,9 @@ var ( // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { host := strings.TrimSpace(val) - // The empty string means default and is not handled by parseDockerDaemonHost + // The empty string means default and is not handled by parseDaemonHost if host != "" { - _, err := parseDockerDaemonHost(host) + _, err := parseDaemonHost(host) if err != nil { return val, err } @@ -41,18 +44,26 @@ func ValidateHost(val string) (string, error) { return val, nil } -// ParseHost and set defaults for a Daemon host string -func ParseHost(defaultToTLS bool, val string) (string, error) { +// ParseHost and set defaults for a Daemon host string. +// defaultToTLS is preferred over defaultToUnixRootless. +func ParseHost(defaultToTLS, defaultToUnixRootless bool, val string) (string, error) { host := strings.TrimSpace(val) if host == "" { if defaultToTLS { host = DefaultTLSHost + } else if defaultToUnixRootless { + runtimeDir, err := homedir.GetRuntimeDir() + if err != nil { + return "", err + } + socket := filepath.Join(runtimeDir, "docker.sock") + host = "unix://" + socket } else { host = DefaultHost } } else { var err error - host, err = parseDockerDaemonHost(host) + host, err = parseDaemonHost(host) if err != nil { return val, err } @@ -60,9 +71,9 @@ func ParseHost(defaultToTLS bool, val string) (string, error) { return host, nil } -// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. +// parseDaemonHost parses the specified address and returns an address that will be used as the host. // Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. -func parseDockerDaemonHost(addr string) (string, error) { +func parseDaemonHost(addr string) (string, error) { addrParts := strings.SplitN(addr, "://", 2) if len(addrParts) == 1 && addrParts[0] != "" { addrParts = []string{"tcp", addrParts[0]} diff --git a/vendor/github.com/docker/docker/opts/hosts_unix.go b/vendor/github.com/docker/docker/opts/hosts_unix.go index 611407a9d..9d5bb6456 100644 --- a/vendor/github.com/docker/docker/opts/hosts_unix.go +++ b/vendor/github.com/docker/docker/opts/hosts_unix.go @@ -1,6 +1,6 @@ // +build !windows -package opts +package opts // import "github.com/docker/docker/opts" import "fmt" diff --git a/vendor/github.com/docker/docker/opts/hosts_windows.go b/vendor/github.com/docker/docker/opts/hosts_windows.go index 7c239e00f..906eba53e 100644 --- a/vendor/github.com/docker/docker/opts/hosts_windows.go +++ b/vendor/github.com/docker/docker/opts/hosts_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package opts +package opts // import "github.com/docker/docker/opts" // DefaultHost constant defines the default host string used by docker on Windows var DefaultHost = "npipe://" + DefaultNamedPipe diff --git a/vendor/github.com/docker/docker/opts/ip.go b/vendor/github.com/docker/docker/opts/ip.go index 109506397..cfbff3a9f 100644 --- a/vendor/github.com/docker/docker/opts/ip.go +++ b/vendor/github.com/docker/docker/opts/ip.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" diff --git a/vendor/github.com/docker/docker/opts/opts.go b/vendor/github.com/docker/docker/opts/opts.go index a86d74d60..de8aacb80 100644 --- a/vendor/github.com/docker/docker/opts/opts.go +++ b/vendor/github.com/docker/docker/opts/opts.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" @@ -7,7 +7,7 @@ import ( "regexp" "strings" - units "github.com/docker/go-units" + "github.com/docker/go-units" ) var ( @@ -52,7 +52,7 @@ func (opts *ListOpts) Set(value string) error { } value = v } - (*opts.values) = append((*opts.values), value) + *opts.values = append(*opts.values, value) return nil } @@ -60,7 +60,7 @@ func (opts *ListOpts) Set(value string) error { func (opts *ListOpts) Delete(key string) { for i, k := range *opts.values { if k == key { - (*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...) + *opts.values = append((*opts.values)[:i], (*opts.values)[i+1:]...) return } } @@ -78,7 +78,7 @@ func (opts *ListOpts) GetMap() map[string]struct{} { // GetAll returns the values of slice. func (opts *ListOpts) GetAll() []string { - return (*opts.values) + return *opts.values } // GetAllOrEmpty returns the values of the slice @@ -103,7 +103,7 @@ func (opts *ListOpts) Get(key string) bool { // Len returns the amount of element in the slice. func (opts *ListOpts) Len() int { - return len((*opts.values)) + return len(*opts.values) } // Type returns a string name for this Option type @@ -263,6 +263,16 @@ func ValidateLabel(val string) (string, error) { return val, nil } +// ValidateSingleGenericResource validates that a single entry in the +// generic resource list is valid. +// i.e 'GPU=UID1' is valid however 'GPU:UID1' or 'UID1' isn't +func ValidateSingleGenericResource(val string) (string, error) { + if strings.Count(val, "=") < 1 { + return "", fmt.Errorf("invalid node-generic-resource format `%s` expected `name=value`", val) + } + return val, nil +} + // ParseLink parses and validates the specified string as a link format (name:alias) func ParseLink(val string) (string, string, error) { if val == "" { diff --git a/vendor/github.com/docker/docker/opts/opts_unix.go b/vendor/github.com/docker/docker/opts/opts_unix.go index 2766a43a0..0c32367cb 100644 --- a/vendor/github.com/docker/docker/opts/opts_unix.go +++ b/vendor/github.com/docker/docker/opts/opts_unix.go @@ -1,6 +1,6 @@ // +build !windows -package opts +package opts // import "github.com/docker/docker/opts" // DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080 const DefaultHTTPHost = "localhost" diff --git a/vendor/github.com/docker/docker/opts/opts_windows.go b/vendor/github.com/docker/docker/opts/opts_windows.go index 98b7251a9..0e1b6c6d1 100644 --- a/vendor/github.com/docker/docker/opts/opts_windows.go +++ b/vendor/github.com/docker/docker/opts/opts_windows.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" // TODO Windows. Identify bug in GOLang 1.5.1+ and/or Windows Server 2016 TP5. // @jhowardmsft, @swernli. diff --git a/vendor/github.com/docker/docker/opts/quotedstring.go b/vendor/github.com/docker/docker/opts/quotedstring.go index 09c68a526..6c889070e 100644 --- a/vendor/github.com/docker/docker/opts/quotedstring.go +++ b/vendor/github.com/docker/docker/opts/quotedstring.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" // QuotedString is a string that may have extra quotes around the value. The // quotes are stripped from the value. diff --git a/vendor/github.com/docker/docker/opts/runtime.go b/vendor/github.com/docker/docker/opts/runtime.go index 4361b3ce0..4b9babf0a 100644 --- a/vendor/github.com/docker/docker/opts/runtime.go +++ b/vendor/github.com/docker/docker/opts/runtime.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" diff --git a/vendor/github.com/docker/docker/opts/ulimit.go b/vendor/github.com/docker/docker/opts/ulimit.go index a2a65fcd2..0e2a36236 100644 --- a/vendor/github.com/docker/docker/opts/ulimit.go +++ b/vendor/github.com/docker/docker/opts/ulimit.go @@ -1,4 +1,4 @@ -package opts +package opts // import "github.com/docker/docker/opts" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/README.md b/vendor/github.com/docker/docker/pkg/README.md index c4b78a8ad..755cd9683 100644 --- a/vendor/github.com/docker/docker/pkg/README.md +++ b/vendor/github.com/docker/docker/pkg/README.md @@ -1,8 +1,8 @@ -pkg/ is a collection of utility packages used by the Docker project without being specific to its internals. +pkg/ is a collection of utility packages used by the Moby project without being specific to its internals. -Utility packages are kept separate from the docker core codebase to keep it as small and concise as possible. +Utility packages are kept separate from the moby core codebase to keep it as small and concise as possible. If some utilities grow larger and their APIs stabilize, they may be moved to their own repository under the -Docker organization, to facilitate re-use by other projects. However that is not the priority. +Moby organization, to facilitate re-use by other projects. However that is not the priority. The directory `pkg` is named after the same directory in the camlistore project. Since Brad is a core Go maintainer, we thought it made sense to copy his methods for organizing Go code :) Thanks Brad! diff --git a/vendor/github.com/docker/docker/pkg/fileutils/fileutils.go b/vendor/github.com/docker/docker/pkg/fileutils/fileutils.go index a129e654e..34f1c726f 100644 --- a/vendor/github.com/docker/docker/pkg/fileutils/fileutils.go +++ b/vendor/github.com/docker/docker/pkg/fileutils/fileutils.go @@ -1,4 +1,4 @@ -package fileutils +package fileutils // import "github.com/docker/docker/pkg/fileutils" import ( "errors" @@ -13,7 +13,7 @@ import ( "github.com/sirupsen/logrus" ) -// PatternMatcher allows checking paths agaist a list of patterns +// PatternMatcher allows checking paths against a list of patterns type PatternMatcher struct { patterns []*Pattern exclusions bool @@ -106,7 +106,7 @@ func (pm *PatternMatcher) Patterns() []*Pattern { return pm.patterns } -// Pattern defines a single regexp used used to filter file paths. +// Pattern defines a single regexp used to filter file paths. type Pattern struct { cleanedPattern string dirs []string diff --git a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go index ccd648fac..e40cc271b 100644 --- a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go +++ b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go @@ -1,4 +1,4 @@ -package fileutils +package fileutils // import "github.com/docker/docker/pkg/fileutils" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go deleted file mode 100644 index 0f2cb7ab9..000000000 --- a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go +++ /dev/null @@ -1,7 +0,0 @@ -package fileutils - -// GetTotalUsedFds Returns the number of used File Descriptors. -// On Solaris these limits are per process and not systemwide -func GetTotalUsedFds() int { - return -1 -} diff --git a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go index 9e0e97bd6..565396f1c 100644 --- a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go +++ b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go @@ -1,6 +1,6 @@ // +build linux freebsd -package fileutils +package fileutils // import "github.com/docker/docker/pkg/fileutils" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go index 5ec21cace..3f1ebb656 100644 --- a/vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go +++ b/vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go @@ -1,4 +1,4 @@ -package fileutils +package fileutils // import "github.com/docker/docker/pkg/fileutils" // GetTotalUsedFds Returns the number of used File Descriptors. Not supported // on Windows. diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go index 012fe52a2..47ecd0c09 100644 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go @@ -1,9 +1,10 @@ -// +build linux - -package homedir +package homedir // import "github.com/docker/docker/pkg/homedir" import ( + "errors" "os" + "path/filepath" + "strings" "github.com/docker/docker/pkg/idtools" ) @@ -21,3 +22,88 @@ func GetStatic() (string, error) { } return usr.Home, nil } + +// GetRuntimeDir returns XDG_RUNTIME_DIR. +// XDG_RUNTIME_DIR is typically configured via pam_systemd. +// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetRuntimeDir() (string, error) { + if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" { + return xdgRuntimeDir, nil + } + return "", errors.New("could not get XDG_RUNTIME_DIR") +} + +// StickRuntimeDirContents sets the sticky bit on files that are under +// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system. +// +// StickyRuntimeDir returns slice of sticked files. +// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func StickRuntimeDirContents(files []string) ([]string, error) { + runtimeDir, err := GetRuntimeDir() + if err != nil { + // ignore error if runtimeDir is empty + return nil, nil + } + runtimeDir, err = filepath.Abs(runtimeDir) + if err != nil { + return nil, err + } + var sticked []string + for _, f := range files { + f, err = filepath.Abs(f) + if err != nil { + return sticked, err + } + if strings.HasPrefix(f, runtimeDir+"/") { + if err = stick(f); err != nil { + return sticked, err + } + sticked = append(sticked, f) + } + } + return sticked, nil +} + +func stick(f string) error { + st, err := os.Stat(f) + if err != nil { + return err + } + m := st.Mode() + m |= os.ModeSticky + return os.Chmod(f, m) +} + +// GetDataHome returns XDG_DATA_HOME. +// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetDataHome() (string, error) { + if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" { + return xdgDataHome, nil + } + home := os.Getenv("HOME") + if home == "" { + return "", errors.New("could not get either XDG_DATA_HOME or HOME") + } + return filepath.Join(home, ".local", "share"), nil +} + +// GetConfigHome returns XDG_CONFIG_HOME. +// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set. +// +// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html +func GetConfigHome() (string, error) { + if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" { + return xdgConfigHome, nil + } + home := os.Getenv("HOME") + if home == "" { + return "", errors.New("could not get either XDG_CONFIG_HOME or HOME") + } + return filepath.Join(home, ".config"), nil +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go index 6b96b856f..f0a363ded 100644 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go @@ -1,6 +1,6 @@ // +build !linux -package homedir +package homedir // import "github.com/docker/docker/pkg/homedir" import ( "errors" @@ -11,3 +11,23 @@ import ( func GetStatic() (string, error) { return "", errors.New("homedir.GetStatic() is not supported on this system") } + +// GetRuntimeDir is unsupported on non-linux system. +func GetRuntimeDir() (string, error) { + return "", errors.New("homedir.GetRuntimeDir() is not supported on this system") +} + +// StickRuntimeDirContents is unsupported on non-linux system. +func StickRuntimeDirContents(files []string) ([]string, error) { + return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system") +} + +// GetDataHome is unsupported on non-linux system. +func GetDataHome() (string, error) { + return "", errors.New("homedir.GetDataHome() is not supported on this system") +} + +// GetConfigHome is unsupported on non-linux system. +func GetConfigHome() (string, error) { + return "", errors.New("homedir.GetConfigHome() is not supported on this system") +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go index f2a20ea8f..d85e12448 100644 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go @@ -1,6 +1,6 @@ // +build !windows -package homedir +package homedir // import "github.com/docker/docker/pkg/homedir" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go index fafdb2bbf..2f81813b2 100644 --- a/vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go @@ -1,4 +1,4 @@ -package homedir +package homedir // import "github.com/docker/docker/pkg/homedir" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/idtools/idtools.go b/vendor/github.com/docker/docker/pkg/idtools/idtools.go index 68a072db2..230422eac 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/idtools.go +++ b/vendor/github.com/docker/docker/pkg/idtools/idtools.go @@ -1,4 +1,4 @@ -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import ( "bufio" @@ -30,43 +30,30 @@ func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] } func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start } const ( - subuidFileName string = "/etc/subuid" - subgidFileName string = "/etc/subgid" + subuidFileName = "/etc/subuid" + subgidFileName = "/etc/subgid" ) -// MkdirAllAs creates a directory (include any along the path) and then modifies -// ownership to the requested uid/gid. If the directory already exists, this -// function will still change ownership to the requested uid/gid pair. -// Deprecated: Use MkdirAllAndChown -func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { - return mkdirAs(path, mode, ownerUID, ownerGID, true, true) -} - -// MkdirAs creates a directory and then modifies ownership to the requested uid/gid. -// If the directory already exists, this function still changes ownership -// Deprecated: Use MkdirAndChown with a IDPair -func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error { - return mkdirAs(path, mode, ownerUID, ownerGID, false, true) -} - // MkdirAllAndChown creates a directory (include any along the path) and then modifies // ownership to the requested uid/gid. If the directory already exists, this // function will still change ownership to the requested uid/gid pair. -func MkdirAllAndChown(path string, mode os.FileMode, ids IDPair) error { - return mkdirAs(path, mode, ids.UID, ids.GID, true, true) +func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, true, true) } // MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid. -// If the directory already exists, this function still changes ownership -func MkdirAndChown(path string, mode os.FileMode, ids IDPair) error { - return mkdirAs(path, mode, ids.UID, ids.GID, false, true) +// If the directory already exists, this function still changes ownership. +// Note that unlike os.Mkdir(), this function does not return IsExist error +// in case path already exists. +func MkdirAndChown(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, false, true) } // MkdirAllAndChownNew creates a directory (include any along the path) and then modifies // ownership ONLY of newly created directories to the requested uid/gid. If the // directories along the path exist, no change of ownership will be performed -func MkdirAllAndChownNew(path string, mode os.FileMode, ids IDPair) error { - return mkdirAs(path, mode, ids.UID, ids.GID, true, false) +func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error { + return mkdirAs(path, mode, owner, true, false) } // GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps. @@ -115,22 +102,23 @@ func toHost(contID int, idMap []IDMap) (int, error) { return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID) } -// IDPair is a UID and GID pair -type IDPair struct { +// Identity is either a UID and GID pair or a SID (but not both) +type Identity struct { UID int GID int + SID string } -// IDMappings contains a mappings of UIDs and GIDs -type IDMappings struct { +// IdentityMapping contains a mappings of UIDs and GIDs +type IdentityMapping struct { uids []IDMap gids []IDMap } -// NewIDMappings takes a requested user and group name and +// NewIdentityMapping takes a requested user and group name and // using the data from /etc/sub{uid,gid} ranges, creates the // proper uid and gid remapping ranges for that user/group pair -func NewIDMappings(username, groupname string) (*IDMappings, error) { +func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) { subuidRanges, err := parseSubuid(username) if err != nil { return nil, err @@ -146,7 +134,7 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) { return nil, fmt.Errorf("No subgid ranges found for group %q", groupname) } - return &IDMappings{ + return &IdentityMapping{ uids: createIDMap(subuidRanges), gids: createIDMap(subgidRanges), }, nil @@ -154,21 +142,21 @@ func NewIDMappings(username, groupname string) (*IDMappings, error) { // NewIDMappingsFromMaps creates a new mapping from two slices // Deprecated: this is a temporary shim while transitioning to IDMapping -func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IDMappings { - return &IDMappings{uids: uids, gids: gids} +func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping { + return &IdentityMapping{uids: uids, gids: gids} } // RootPair returns a uid and gid pair for the root user. The error is ignored // because a root user always exists, and the defaults are correct when the uid // and gid maps are empty. -func (i *IDMappings) RootPair() IDPair { +func (i *IdentityMapping) RootPair() Identity { uid, gid, _ := GetRootUIDGID(i.uids, i.gids) - return IDPair{UID: uid, GID: gid} + return Identity{UID: uid, GID: gid} } // ToHost returns the host UID and GID for the container uid, gid. // Remapping is only performed if the ids aren't already the remapped root ids -func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { +func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) { var err error target := i.RootPair() @@ -186,7 +174,7 @@ func (i *IDMappings) ToHost(pair IDPair) (IDPair, error) { } // ToContainer returns the container UID and GID for the host uid and gid -func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { +func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) { uid, err := toContainer(pair.UID, i.uids) if err != nil { return -1, -1, err @@ -196,19 +184,19 @@ func (i *IDMappings) ToContainer(pair IDPair) (int, int, error) { } // Empty returns true if there are no id mappings -func (i *IDMappings) Empty() bool { +func (i *IdentityMapping) Empty() bool { return len(i.uids) == 0 && len(i.gids) == 0 } // UIDs return the UID mapping // TODO: remove this once everything has been refactored to use pairs -func (i *IDMappings) UIDs() []IDMap { +func (i *IdentityMapping) UIDs() []IDMap { return i.uids } // GIDs return the UID mapping // TODO: remove this once everything has been refactored to use pairs -func (i *IDMappings) GIDs() []IDMap { +func (i *IdentityMapping) GIDs() []IDMap { return i.gids } diff --git a/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go b/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go index 8701bb7fa..fb239743a 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go +++ b/vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go @@ -1,6 +1,6 @@ // +build !windows -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import ( "bytes" @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" "sync" + "syscall" "github.com/docker/docker/pkg/system" "github.com/opencontainers/runc/libcontainer/user" @@ -20,20 +21,29 @@ var ( getentCmd string ) -func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { +func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { // make an array containing the original path asked for, plus (for mkAll == true) // all path components leading up to the complete path that don't exist before we MkdirAll // so that we can chown all of them properly at the end. If chownExisting is false, we won't // chown the full directory path if it exists + var paths []string - if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { - paths = []string{path} - } else if err == nil && chownExisting { + + stat, err := system.Stat(path) + if err == nil { + if !stat.IsDir() { + return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} + } + if !chownExisting { + return nil + } + // short-circuit--we were called with an existing directory and chown was requested - return os.Chown(path, ownerUID, ownerGID) - } else if err == nil { - // nothing to do; directory path fully exists already and chown was NOT requested - return nil + return lazyChown(path, owner.UID, owner.GID, stat) + } + + if os.IsNotExist(err) { + paths = []string{path} } if mkAll { @@ -49,7 +59,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown paths = append(paths, dirPath) } } - if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) { + if err := system.MkdirAll(path, mode, ""); err != nil { return err } } else { @@ -60,7 +70,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // even if it existed, we will chown the requested path + any subpaths that // didn't exist when we called MkdirAll for _, pathComponent := range paths { - if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil { + if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil { return err } } @@ -69,7 +79,7 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory -func CanAccess(path string, pair IDPair) bool { +func CanAccess(path string, pair Identity) bool { statInfo, err := system.Stat(path) if err != nil { return false @@ -202,3 +212,20 @@ func callGetent(args string) (io.Reader, error) { } return bytes.NewReader(out), nil } + +// lazyChown performs a chown only if the uid/gid don't match what's requested +// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the +// dir is on an NFS share, so don't call chown unless we absolutely must. +func lazyChown(p string, uid, gid int, stat *system.StatT) error { + if stat == nil { + var err error + stat, err = system.Stat(p) + if err != nil { + return err + } + } + if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) { + return nil + } + return os.Chown(p, uid, gid) +} diff --git a/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go b/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go index 45d2878e3..4ae38a1b1 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go +++ b/vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import ( "os" @@ -8,10 +6,12 @@ import ( "github.com/docker/docker/pkg/system" ) -// Platforms such as Windows do not support the UID/GID concept. So make this -// just a wrapper around system.MkdirAll. -func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error { - if err := system.MkdirAll(path, mode, ""); err != nil && !os.IsExist(err) { +// This is currently a wrapper around MkdirAll, however, since currently +// permissions aren't set through this path, the identity isn't utilized. +// Ownership is handled elsewhere, but in the future could be support here +// too. +func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error { + if err := system.MkdirAll(path, mode, ""); err != nil { return err } return nil @@ -20,6 +20,6 @@ func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chown // CanAccess takes a valid (existing) directory and a uid, gid pair and determines // if that uid, gid pair has access (execute bit) to the directory // Windows does not require/support this function, so always return true -func CanAccess(path string, pair IDPair) bool { +func CanAccess(path string, identity Identity) bool { return true } diff --git a/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go b/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go index 9da7975e2..6272c5a40 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go +++ b/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go @@ -1,4 +1,4 @@ -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go b/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go index d98b354cb..e7c4d6311 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go @@ -1,6 +1,6 @@ // +build !linux -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import "fmt" diff --git a/vendor/github.com/docker/docker/pkg/idtools/utils_unix.go b/vendor/github.com/docker/docker/pkg/idtools/utils_unix.go index 9703ecbd9..903ac4501 100644 --- a/vendor/github.com/docker/docker/pkg/idtools/utils_unix.go +++ b/vendor/github.com/docker/docker/pkg/idtools/utils_unix.go @@ -1,6 +1,6 @@ // +build !windows -package idtools +package idtools // import "github.com/docker/docker/pkg/idtools" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/buffer.go b/vendor/github.com/docker/docker/pkg/ioutils/buffer.go index 3d737b3e1..466f79294 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/buffer.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/buffer.go @@ -1,4 +1,4 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( "errors" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go b/vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go index 72a04f349..d4bbf3c9d 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go @@ -1,4 +1,4 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( "errors" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/fswriters.go b/vendor/github.com/docker/docker/pkg/ioutils/fswriters.go index a56c46265..534d66ac2 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/fswriters.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/fswriters.go @@ -1,4 +1,4 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( "io" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/readers.go b/vendor/github.com/docker/docker/pkg/ioutils/readers.go index 63f3c07f4..1f657bd3d 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/readers.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/readers.go @@ -1,25 +1,28 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( + "context" "crypto/sha256" "encoding/hex" "io" - - "golang.org/x/net/context" ) -type readCloserWrapper struct { +// ReadCloserWrapper wraps an io.Reader, and implements an io.ReadCloser +// It calls the given callback function when closed. It should be constructed +// with NewReadCloserWrapper +type ReadCloserWrapper struct { io.Reader closer func() error } -func (r *readCloserWrapper) Close() error { +// Close calls back the passed closer function +func (r *ReadCloserWrapper) Close() error { return r.closer() } // NewReadCloserWrapper returns a new io.ReadCloser. func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { - return &readCloserWrapper{ + return &ReadCloserWrapper{ Reader: r, closer: closer, } diff --git a/vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go b/vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go index 1539ad21b..dc894f913 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go @@ -1,6 +1,6 @@ // +build !windows -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import "io/ioutil" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go b/vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go index c258e5fdd..ecaba2e36 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( "io/ioutil" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go b/vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go index 52a4901ad..91b8d1826 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go @@ -1,4 +1,4 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import ( "io" diff --git a/vendor/github.com/docker/docker/pkg/ioutils/writers.go b/vendor/github.com/docker/docker/pkg/ioutils/writers.go index ccc7f9c23..61c679497 100644 --- a/vendor/github.com/docker/docker/pkg/ioutils/writers.go +++ b/vendor/github.com/docker/docker/pkg/ioutils/writers.go @@ -1,4 +1,4 @@ -package ioutils +package ioutils // import "github.com/docker/docker/pkg/ioutils" import "io" diff --git a/vendor/github.com/docker/docker/pkg/longpath/longpath.go b/vendor/github.com/docker/docker/pkg/longpath/longpath.go index 9b15bfff4..4177affba 100644 --- a/vendor/github.com/docker/docker/pkg/longpath/longpath.go +++ b/vendor/github.com/docker/docker/pkg/longpath/longpath.go @@ -2,7 +2,7 @@ // in Windows, which are expected to be prepended with `\\?\` and followed by either // a drive letter, a UNC server\share, or a volume identifier. -package longpath +package longpath // import "github.com/docker/docker/pkg/longpath" import ( "strings" diff --git a/vendor/github.com/docker/docker/pkg/mount/flags.go b/vendor/github.com/docker/docker/pkg/mount/flags.go index 607dbed43..ffd473311 100644 --- a/vendor/github.com/docker/docker/pkg/mount/flags.go +++ b/vendor/github.com/docker/docker/pkg/mount/flags.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "fmt" @@ -135,15 +135,3 @@ func parseOptions(options string) (int, string) { } return flag, strings.Join(data, ",") } - -// ParseTmpfsOptions parse fstab type mount options into flags and data -func ParseTmpfsOptions(options string) (int, string, error) { - flags, data := parseOptions(options) - for _, o := range strings.Split(data, ",") { - opt := strings.SplitN(o, "=", 2) - if !validFlags[opt[0]] { - return 0, "", fmt.Errorf("Invalid tmpfs option %q", opt) - } - } - return flags, data, nil -} diff --git a/vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go b/vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go index 5f76f331b..ef35ef905 100644 --- a/vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go @@ -1,6 +1,6 @@ // +build freebsd,cgo -package mount +package mount // import "github.com/docker/docker/pkg/mount" /* #include <sys/mount.h> diff --git a/vendor/github.com/docker/docker/pkg/mount/flags_linux.go b/vendor/github.com/docker/docker/pkg/mount/flags_linux.go index 0425d0dd6..a1b199a31 100644 --- a/vendor/github.com/docker/docker/pkg/mount/flags_linux.go +++ b/vendor/github.com/docker/docker/pkg/mount/flags_linux.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go b/vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go index 9ed741e3f..cc6c47590 100644 --- a/vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go @@ -1,6 +1,6 @@ -// +build !linux,!freebsd freebsd,!cgo solaris,!cgo +// +build !linux,!freebsd freebsd,!cgo -package mount +package mount // import "github.com/docker/docker/pkg/mount" // These flags are unsupported. const ( diff --git a/vendor/github.com/docker/docker/pkg/mount/mount.go b/vendor/github.com/docker/docker/pkg/mount/mount.go index c9fdfd694..4afd63c42 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mount.go +++ b/vendor/github.com/docker/docker/pkg/mount/mount.go @@ -1,30 +1,100 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "sort" + "strconv" "strings" + + "github.com/sirupsen/logrus" ) -// GetMounts retrieves a list of mounts for the current running process. -func GetMounts() ([]*Info, error) { - return parseMountTable() +// mountError records an error from mount or unmount operation +type mountError struct { + op string + source, target string + flags uintptr + data string + err error +} + +func (e *mountError) Error() string { + out := e.op + " " + + if e.source != "" { + out += e.source + ":" + e.target + } else { + out += e.target + } + + if e.flags != uintptr(0) { + out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16) + } + if e.data != "" { + out += ", data: " + e.data + } + + out += ": " + e.err.Error() + return out +} + +// Cause returns the underlying cause of the error +func (e *mountError) Cause() error { + return e.err +} + +// FilterFunc is a type defining a callback function +// to filter out unwanted entries. It takes a pointer +// to an Info struct (not fully populated, currently +// only Mountpoint is filled in), and returns two booleans: +// - skip: true if the entry should be skipped +// - stop: true if parsing should be stopped after the entry +type FilterFunc func(*Info) (skip, stop bool) + +// PrefixFilter discards all entries whose mount points +// do not start with a prefix specified +func PrefixFilter(prefix string) FilterFunc { + return func(m *Info) (bool, bool) { + skip := !strings.HasPrefix(m.Mountpoint, prefix) + return skip, false + } +} + +// SingleEntryFilter looks for a specific entry +func SingleEntryFilter(mp string) FilterFunc { + return func(m *Info) (bool, bool) { + if m.Mountpoint == mp { + return false, true // don't skip, stop now + } + return true, false // skip, keep going + } +} + +// ParentsFilter returns all entries whose mount points +// can be parents of a path specified, discarding others. +// For example, given `/var/lib/docker/something`, entries +// like `/var/lib/docker`, `/var` and `/` are returned. +func ParentsFilter(path string) FilterFunc { + return func(m *Info) (bool, bool) { + skip := !strings.HasPrefix(path, m.Mountpoint) + return skip, false + } +} + +// GetMounts retrieves a list of mounts for the current running process, +// with an optional filter applied (use nil for no filter). +func GetMounts(f FilterFunc) ([]*Info, error) { + return parseMountTable(f) } // Mounted determines if a specified mountpoint has been mounted. -// On Linux it looks at /proc/self/mountinfo and on Solaris at mnttab. +// On Linux it looks at /proc/self/mountinfo. func Mounted(mountpoint string) (bool, error) { - entries, err := parseMountTable() + entries, err := GetMounts(SingleEntryFilter(mountpoint)) if err != nil { return false, err } - // Search the table for the mountpoint - for _, e := range entries { - if e.Mountpoint == mountpoint { - return true, nil - } - } - return false, nil + return len(entries) > 0, nil } // Mount will mount filesystem according to the specified configuration, on the @@ -53,34 +123,37 @@ func ForceMount(device, target, mType, options string) error { // Unmount lazily unmounts a filesystem on supported platforms, otherwise // does a normal unmount. func Unmount(target string) error { - if mounted, err := Mounted(target); err != nil || !mounted { - return err - } return unmount(target, mntDetach) } // RecursiveUnmount unmounts the target and all mounts underneath, starting with // the deepsest mount first. func RecursiveUnmount(target string) error { - mounts, err := GetMounts() + mounts, err := parseMountTable(PrefixFilter(target)) if err != nil { return err } // Make the deepest mount be first - sort.Sort(sort.Reverse(byMountpoint(mounts))) + sort.Slice(mounts, func(i, j int) bool { + return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint) + }) for i, m := range mounts { - if !strings.HasPrefix(m.Mountpoint, target) { - continue - } - if err := Unmount(m.Mountpoint); err != nil && i == len(mounts)-1 { - if mounted, err := Mounted(m.Mountpoint); err != nil || mounted { - return err + logrus.Debugf("Trying to unmount %s", m.Mountpoint) + err = unmount(m.Mountpoint, mntDetach) + if err != nil { + if i == len(mounts)-1 { // last mount + if mounted, e := Mounted(m.Mountpoint); e != nil || mounted { + return err + } + } else { + // This is some submount, we can ignore this error for now, the final unmount will fail if this is a real problem + logrus.WithError(err).Warnf("Failed to unmount submount %s", m.Mountpoint) } - // Ignore errors for submounts and continue trying to unmount others - // The final unmount should fail if there ane any submounts remaining } + + logrus.Debugf("Unmounted %s", m.Mountpoint) } return nil } diff --git a/vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go b/vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go index 814896cc9..09ad36060 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" /* #include <errno.h> @@ -11,11 +11,9 @@ package mount import "C" import ( - "fmt" "strings" + "syscall" "unsafe" - - "golang.org/x/sys/unix" ) func allocateIOVecs(options []string) []C.struct_iovec { @@ -49,12 +47,13 @@ func mount(device, target, mType string, flag uintptr, data string) error { } if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 { - reason := C.GoString(C.strerror(*C.__error())) - return fmt.Errorf("Failed to call nmount: %s", reason) + return &mountError{ + op: "mount", + source: device, + target: target, + flags: flag, + err: syscall.Errno(errno), + } } return nil } - -func unmount(target string, flag int) error { - return unix.Unmount(target, flag) -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mounter_linux.go b/vendor/github.com/docker/docker/pkg/mount/mounter_linux.go index 39c36d472..a0a1ad236 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mounter_linux.go +++ b/vendor/github.com/docker/docker/pkg/mount/mounter_linux.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "golang.org/x/sys/unix" @@ -33,25 +33,41 @@ func mount(device, target, mType string, flags uintptr, data string) error { // Initial call applying all non-propagation flags for mount // or remount with changed data if err := unix.Mount(device, target, mType, oflags, data); err != nil { - return err + return &mountError{ + op: "mount", + source: device, + target: target, + flags: oflags, + data: data, + err: err, + } } } if flags&ptypes != 0 { // Change the propagation type. if err := unix.Mount("", target, "", flags&pflags, ""); err != nil { - return err + return &mountError{ + op: "remount", + target: target, + flags: flags & pflags, + err: err, + } } } if oflags&broflags == broflags { // Remount the bind to apply read only. - return unix.Mount("", target, "", oflags|unix.MS_REMOUNT, "") + if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil { + return &mountError{ + op: "remount-ro", + target: target, + flags: oflags | unix.MS_REMOUNT, + err: err, + } + + } } return nil } - -func unmount(target string, flag int) error { - return unix.Unmount(target, flag) -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mounter_solaris.go b/vendor/github.com/docker/docker/pkg/mount/mounter_solaris.go deleted file mode 100644 index 48b86771e..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/mounter_solaris.go +++ /dev/null @@ -1,34 +0,0 @@ -// +build solaris,cgo - -package mount - -import ( - "unsafe" - - "golang.org/x/sys/unix" -) - -// #include <stdlib.h> -// #include <stdio.h> -// #include <sys/mount.h> -// int Mount(const char *spec, const char *dir, int mflag, -// char *fstype, char *dataptr, int datalen, char *optptr, int optlen) { -// return mount(spec, dir, mflag, fstype, dataptr, datalen, optptr, optlen); -// } -import "C" - -func mount(device, target, mType string, flag uintptr, data string) error { - spec := C.CString(device) - dir := C.CString(target) - fstype := C.CString(mType) - _, err := C.Mount(spec, dir, C.int(flag), fstype, nil, 0, nil, 0) - C.free(unsafe.Pointer(spec)) - C.free(unsafe.Pointer(dir)) - C.free(unsafe.Pointer(fstype)) - return err -} - -func unmount(target string, flag int) error { - err := unix.Unmount(target, flag) - return err -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go b/vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go index a2a3bb457..c3e5aec27 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go @@ -1,11 +1,7 @@ -// +build !linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo +// +build !linux,!freebsd freebsd,!cgo -package mount +package mount // import "github.com/docker/docker/pkg/mount" func mount(device, target, mType string, flag uintptr, data string) error { panic("Not implemented") } - -func unmount(target string, flag int) error { - panic("Not implemented") -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo.go index ff4cc1d86..ecd03fc02 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo.go +++ b/vendor/github.com/docker/docker/pkg/mount/mountinfo.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" // Info reveals information about a particular mounted filesystem. This // struct is populated from the content in the /proc/<pid>/mountinfo file. @@ -38,17 +38,3 @@ type Info struct { // VfsOpts represents per super block options. VfsOpts string } - -type byMountpoint []*Info - -func (by byMountpoint) Len() int { - return len(by) -} - -func (by byMountpoint) Less(i, j int) bool { - return by[i].Mountpoint < by[j].Mountpoint -} - -func (by byMountpoint) Swap(i, j int) { - by[i], by[j] = by[j], by[i] -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go index 4f32edcd9..36c89dc1a 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go @@ -1,4 +1,4 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" /* #include <sys/param.h> @@ -15,7 +15,7 @@ import ( // Parse /proc/self/mountinfo because comparing Dev and ino does not work from // bind mounts. -func parseMountTable() ([]*Info, error) { +func parseMountTable(filter FilterFunc) ([]*Info, error) { var rawEntries *C.struct_statfs count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT)) @@ -32,10 +32,24 @@ func parseMountTable() ([]*Info, error) { var out []*Info for _, entry := range entries { var mountinfo Info + var skip, stop bool mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0]) + + if filter != nil { + // filter out entries we're not interested in + skip, stop = filter(p) + if skip { + continue + } + } + mountinfo.Source = C.GoString(&entry.f_mntfromname[0]) mountinfo.Fstype = C.GoString(&entry.f_fstypename[0]) + out = append(out, &mountinfo) + if stop { + break + } } return out, nil } diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go index be69fee1d..c1dba01fc 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go +++ b/vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go @@ -1,86 +1,123 @@ -// +build linux - -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "bufio" "fmt" "io" "os" + "strconv" "strings" ) -const ( - /* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue - (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) +func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) { + s := bufio.NewScanner(r) + out := []*Info{} + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + /* + 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue + (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) - (1) mount ID: unique identifier of the mount (may be reused after umount) - (2) parent ID: ID of parent (or of self for the top of the mount tree) - (3) major:minor: value of st_dev for files on filesystem - (4) root: root of the mount within the filesystem - (5) mount point: mount point relative to the process's root - (6) mount options: per mount options - (7) optional fields: zero or more fields of the form "tag[:value]" - (8) separator: marks the end of the optional fields - (9) filesystem type: name of filesystem of the form "type[.subtype]" - (10) mount source: filesystem specific information or "none" - (11) super options: per super block options*/ - mountinfoFormat = "%d %d %d:%d %s %s %s %s" -) + (1) mount ID: unique identifier of the mount (may be reused after umount) + (2) parent ID: ID of parent (or of self for the top of the mount tree) + (3) major:minor: value of st_dev for files on filesystem + (4) root: root of the mount within the filesystem + (5) mount point: mount point relative to the process's root + (6) mount options: per mount options + (7) optional fields: zero or more fields of the form "tag[:value]" + (8) separator: marks the end of the optional fields + (9) filesystem type: name of filesystem of the form "type[.subtype]" + (10) mount source: filesystem specific information or "none" + (11) super options: per super block options + */ -// Parse /proc/self/mountinfo because comparing Dev and ino does not work from -// bind mounts -func parseMountTable() ([]*Info, error) { - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return nil, err - } - defer f.Close() + text := s.Text() + fields := strings.Split(text, " ") + numFields := len(fields) + if numFields < 10 { + // should be at least 10 fields + return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields) + } - return parseInfoFile(f) -} + p := &Info{} + // ignore any numbers parsing errors, as there should not be any + p.ID, _ = strconv.Atoi(fields[0]) + p.Parent, _ = strconv.Atoi(fields[1]) + mm := strings.Split(fields[2], ":") + if len(mm) != 2 { + return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm) + } + p.Major, _ = strconv.Atoi(mm[0]) + p.Minor, _ = strconv.Atoi(mm[1]) -func parseInfoFile(r io.Reader) ([]*Info, error) { - var ( - s = bufio.NewScanner(r) - out = []*Info{} - ) + p.Root = fields[3] + p.Mountpoint = fields[4] + p.Opts = fields[5] - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err + var skip, stop bool + if filter != nil { + // filter out entries we're not interested in + skip, stop = filter(p) + if skip { + continue + } } - var ( - p = &Info{} - text = s.Text() - optionalFields string - ) - - if _, err := fmt.Sscanf(text, mountinfoFormat, - &p.ID, &p.Parent, &p.Major, &p.Minor, - &p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil { - return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err) + // one or more optional fields, when a separator (-) + i := 6 + for ; i < numFields && fields[i] != "-"; i++ { + switch i { + case 6: + p.Optional = fields[6] + default: + /* NOTE there might be more optional fields before the such as + fields[7]...fields[N] (where N < sepIndex), although + as of Linux kernel 4.15 the only known ones are + mount propagation flags in fields[6]. The correct + behavior is to ignore any unknown optional fields. + */ + break + } } - // Safe as mountinfo encodes mountpoints with spaces as \040. - index := strings.Index(text, " - ") - postSeparatorFields := strings.Fields(text[index+3:]) - if len(postSeparatorFields) < 3 { - return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text) + if i == numFields { + return nil, fmt.Errorf("Parsing '%s' failed: missing separator ('-')", text) } - if optionalFields != "-" { - p.Optional = optionalFields + // There should be 3 fields after the separator... + if i+4 > numFields { + return nil, fmt.Errorf("Parsing '%s' failed: not enough fields after a separator", text) } + // ... but in Linux <= 3.9 mounting a cifs with spaces in a share name + // (like "//serv/My Documents") _may_ end up having a space in the last field + // of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs + // option unc= is ignored, so a space should not appear. In here we ignore + // those "extra" fields caused by extra spaces. + p.Fstype = fields[i+1] + p.Source = fields[i+2] + p.VfsOpts = fields[i+3] - p.Fstype = postSeparatorFields[0] - p.Source = postSeparatorFields[1] - p.VfsOpts = strings.Join(postSeparatorFields[2:], " ") out = append(out, p) + if stop { + break + } } return out, nil } +// Parse /proc/self/mountinfo because comparing Dev and ino does not work from +// bind mounts +func parseMountTable(filter FilterFunc) ([]*Info, error) { + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return nil, err + } + defer f.Close() + + return parseInfoFile(f, filter) +} + // PidMountInfo collects the mounts for a specific process ID. If the process // ID is unknown, it is better to use `GetMounts` which will inspect // "/proc/self/mountinfo" instead. @@ -91,5 +128,5 @@ func PidMountInfo(pid int) ([]*Info, error) { } defer f.Close() - return parseInfoFile(f) + return parseInfoFile(f, nil) } diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo_solaris.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo_solaris.go deleted file mode 100644 index 069ed8f2d..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo_solaris.go +++ /dev/null @@ -1,44 +0,0 @@ -// +build solaris,cgo - -package mount - -/* -#include <stdio.h> -#include <stdlib.h> -#include <sys/mnttab.h> -*/ -import "C" - -import ( - "fmt" - "unsafe" -) - -func parseMountTable() ([]*Info, error) { - path := C.CString(C.MNTTAB) - defer C.free(unsafe.Pointer(path)) - mode := C.CString("r") - defer C.free(unsafe.Pointer(mode)) - - mnttab := C.fopen(path, mode) - if mnttab == nil { - return nil, fmt.Errorf("Failed to open %s", C.MNTTAB) - } - - var out []*Info - var mp C.struct_mnttab - - ret := C.getmntent(mnttab, &mp) - for ret == 0 { - var mountinfo Info - mountinfo.Mountpoint = C.GoString(mp.mnt_mountp) - mountinfo.Source = C.GoString(mp.mnt_special) - mountinfo.Fstype = C.GoString(mp.mnt_fstype) - mountinfo.Opts = C.GoString(mp.mnt_mntopts) - out = append(out, &mountinfo) - ret = C.getmntent(mnttab, &mp) - } - - C.fclose(mnttab) - return out, nil -} diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go index 7fbcf1921..fd16d3ed6 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go @@ -1,12 +1,12 @@ -// +build !windows,!linux,!freebsd,!solaris freebsd,!cgo solaris,!cgo +// +build !windows,!linux,!freebsd freebsd,!cgo -package mount +package mount // import "github.com/docker/docker/pkg/mount" import ( "fmt" "runtime" ) -func parseMountTable() ([]*Info, error) { +func parseMountTable(f FilterFunc) ([]*Info, error) { return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) } diff --git a/vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go b/vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go index dab8a37ed..27e0f6976 100644 --- a/vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go +++ b/vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go @@ -1,6 +1,6 @@ -package mount +package mount // import "github.com/docker/docker/pkg/mount" -func parseMountTable() ([]*Info, error) { +func parseMountTable(f FilterFunc) ([]*Info, error) { // Do NOT return an error! return nil, nil } diff --git a/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go b/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go index 8ceec84bc..8a100f0bc 100644 --- a/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go +++ b/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go @@ -1,6 +1,4 @@ -// +build linux - -package mount +package mount // import "github.com/docker/docker/pkg/mount" // MakeShared ensures a mounted filesystem has the SHARED mount option enabled. // See the supported options in flags.go for further reference. @@ -50,18 +48,22 @@ func MakeRUnbindable(mountPoint string) error { return ensureMountedAs(mountPoint, "runbindable") } -func ensureMountedAs(mountPoint, options string) error { - mounted, err := Mounted(mountPoint) +// MakeMount ensures that the file or directory given is a mount point, +// bind mounting it to itself it case it is not. +func MakeMount(mnt string) error { + mounted, err := Mounted(mnt) if err != nil { return err } - - if !mounted { - if err := Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil { - return err - } + if mounted { + return nil } - if _, err = Mounted(mountPoint); err != nil { + + return Mount(mnt, mnt, "none", "bind") +} + +func ensureMountedAs(mountPoint, options string) error { + if err := MakeMount(mountPoint); err != nil { return err } diff --git a/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_solaris.go b/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_solaris.go deleted file mode 100644 index 09f6b03cb..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/sharedsubtree_solaris.go +++ /dev/null @@ -1,58 +0,0 @@ -// +build solaris - -package mount - -// MakeShared ensures a mounted filesystem has the SHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeShared(mountPoint string) error { - return ensureMountedAs(mountPoint, "shared") -} - -// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRShared(mountPoint string) error { - return ensureMountedAs(mountPoint, "rshared") -} - -// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled. -// See the supported options in flags.go for further reference. -func MakePrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, "private") -} - -// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeRPrivate(mountPoint string) error { - return ensureMountedAs(mountPoint, "rprivate") -} - -// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, "slave") -} - -// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled. -// See the supported options in flags.go for further reference. -func MakeRSlave(mountPoint string) error { - return ensureMountedAs(mountPoint, "rslave") -} - -// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option -// enabled. See the supported options in flags.go for further reference. -func MakeUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, "unbindable") -} - -// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount -// option enabled. See the supported options in flags.go for further reference. -func MakeRUnbindable(mountPoint string) error { - return ensureMountedAs(mountPoint, "runbindable") -} - -func ensureMountedAs(mountPoint, options string) error { - // TODO: Solaris does not support bind mounts. - // Evaluate lofs and also look at the relevant - // mount flags to be supported. - return nil -} diff --git a/vendor/github.com/docker/docker/pkg/mount/unmount_unix.go b/vendor/github.com/docker/docker/pkg/mount/unmount_unix.go new file mode 100644 index 000000000..4be427685 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/mount/unmount_unix.go @@ -0,0 +1,22 @@ +// +build !windows + +package mount // import "github.com/docker/docker/pkg/mount" + +import "golang.org/x/sys/unix" + +func unmount(target string, flags int) error { + err := unix.Unmount(target, flags) + if err == nil || err == unix.EINVAL { + // Ignore "not mounted" error here. Note the same error + // can be returned if flags are invalid, so this code + // assumes that the flags value is always correct. + return nil + } + + return &mountError{ + op: "umount", + target: target, + flags: uintptr(flags), + err: err, + } +} diff --git a/vendor/github.com/docker/docker/pkg/mount/unmount_unsupported.go b/vendor/github.com/docker/docker/pkg/mount/unmount_unsupported.go new file mode 100644 index 000000000..a88ad3577 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/mount/unmount_unsupported.go @@ -0,0 +1,7 @@ +// +build windows + +package mount // import "github.com/docker/docker/pkg/mount" + +func unmount(target string, flag int) error { + panic("Not implemented") +} diff --git a/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go b/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go index 2f869ed92..4c6a93d4c 100644 --- a/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go +++ b/vendor/github.com/docker/docker/pkg/namesgenerator/names-generator.go @@ -1,4 +1,4 @@ -package namesgenerator +package namesgenerator // import "github.com/docker/docker/pkg/namesgenerator" import ( "fmt" @@ -15,15 +15,19 @@ var ( "angry", "awesome", "blissful", + "bold", "boring", "brave", + "charming", "clever", "cocky", + "cool", "compassionate", "competent", "condescending", "confident", "cranky", + "crazy", "dazzling", "determined", "distracted", @@ -59,6 +63,7 @@ var ( "laughing", "loving", "lucid", + "magical", "mystifying", "modest", "musing", @@ -75,6 +80,7 @@ var ( "priceless", "quirky", "quizzical", + "recursing", "relaxed", "reverent", "romantic", @@ -86,6 +92,7 @@ var ( "stoic", "stupefied", "suspicious", + "sweet", "tender", "thirsty", "trusting", @@ -114,6 +121,9 @@ var ( // June Almeida - Scottish virologist who took the first pictures of the rubella virus - https://en.wikipedia.org/wiki/June_Almeida "almeida", + // Kathleen Antonelli, American computer programmer and one of the six original programmers of the ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli + "antonelli", + // Maria Gaetana Agnesi - Italian mathematician, philosopher, theologian and humanitarian. She was the first woman to write a mathematics handbook and the first woman appointed as a Mathematics Professor at a University. https://en.wikipedia.org/wiki/Maria_Gaetana_Agnesi "agnesi", @@ -135,6 +145,9 @@ var ( // Stefan Banach - Polish mathematician, was one of the founders of modern functional analysis. https://en.wikipedia.org/wiki/Stefan_Banach "banach", + // Buckaroo Banzai and his mentor Dr. Hikita perfectd the "oscillation overthruster", a device that allows one to pass through solid matter. - https://en.wikipedia.org/wiki/The_Adventures_of_Buckaroo_Banzai_Across_the_8th_Dimension + "banzai", + // John Bardeen co-invented the transistor - https://en.wikipedia.org/wiki/John_Bardeen "bardeen", @@ -159,6 +172,12 @@ var ( // Bhaskara II - Ancient Indian mathematician-astronomer whose work on calculus predates Newton and Leibniz by over half a millennium - https://en.wikipedia.org/wiki/Bh%C4%81skara_II#Calculus "bhaskara", + // Sue Black - British computer scientist and campaigner. She has been instrumental in saving Bletchley Park, the site of World War II codebreaking - https://en.wikipedia.org/wiki/Sue_Black_(computer_scientist) + "black", + + // Elizabeth Helen Blackburn - Australian-American Nobel laureate; best known for co-discovering telomerase. https://en.wikipedia.org/wiki/Elizabeth_Blackburn + "blackburn", + // Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - https://en.wikipedia.org/wiki/Elizabeth_Blackwell "blackwell", @@ -186,14 +205,44 @@ var ( // Emmett Brown invented time travel. https://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff) "brown", + // Linda Brown Buck - American biologist and Nobel laureate best known for her genetic and molecular analyses of the mechanisms of smell. https://en.wikipedia.org/wiki/Linda_B._Buck + "buck", + + // Dame Susan Jocelyn Bell Burnell - Northern Irish astrophysicist who discovered radio pulsars and was the first to analyse them. https://en.wikipedia.org/wiki/Jocelyn_Bell_Burnell + "burnell", + + // Annie Jump Cannon - pioneering female astronomer who classified hundreds of thousands of stars and created the system we use to understand stars today. https://en.wikipedia.org/wiki/Annie_Jump_Cannon + "cannon", + // Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. https://en.wikipedia.org/wiki/Rachel_Carson "carson", + // Dame Mary Lucy Cartwright - British mathematician who was one of the first to study what is now known as chaos theory. Also known for Cartwright's theorem which finds applications in signal processing. https://en.wikipedia.org/wiki/Mary_Cartwright + "cartwright", + + // Vinton Gray Cerf - American Internet pioneer, recognised as one of "the fathers of the Internet". With Robert Elliot Kahn, he designed TCP and IP, the primary data communication protocols of the Internet and other computer networks. https://en.wikipedia.org/wiki/Vint_Cerf + "cerf", + // Subrahmanyan Chandrasekhar - Astrophysicist known for his mathematical theory on different stages and evolution in structures of the stars. He has won nobel prize for physics - https://en.wikipedia.org/wiki/Subrahmanyan_Chandrasekhar "chandrasekhar", - //Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon) - "shannon", + // Sergey Alexeyevich Chaplygin (Russian: Серге́й Алексе́евич Чаплы́гин; April 5, 1869 – October 8, 1942) was a Russian and Soviet physicist, mathematician, and mechanical engineer. He is known for mathematical formulas such as Chaplygin's equation and for a hypothetical substance in cosmology called Chaplygin gas, named after him. https://en.wikipedia.org/wiki/Sergey_Chaplygin + "chaplygin", + + // Émilie du Châtelet - French natural philosopher, mathematician, physicist, and author during the early 1730s, known for her translation of and commentary on Isaac Newton's book Principia containing basic laws of physics. https://en.wikipedia.org/wiki/%C3%89milie_du_Ch%C3%A2telet + "chatelet", + + // Asima Chatterjee was an Indian organic chemist noted for her research on vinca alkaloids, development of drugs for treatment of epilepsy and malaria - https://en.wikipedia.org/wiki/Asima_Chatterjee + "chatterjee", + + // Pafnuty Chebyshev - Russian mathematician. He is known fo his works on probability, statistics, mechanics, analytical geometry and number theory https://en.wikipedia.org/wiki/Pafnuty_Chebyshev + "chebyshev", + + // Bram Cohen - American computer programmer and author of the BitTorrent peer-to-peer protocol. https://en.wikipedia.org/wiki/Bram_Cohen + "cohen", + + // David Lee Chaum - American computer scientist and cryptographer. Known for his seminal contributions in the field of anonymous communication. https://en.wikipedia.org/wiki/David_Chaum + "chaum", // Joan Clarke - Bletchley Park code breaker during the Second World War who pioneered techniques that remained top secret for decades. Also an accomplished numismatist https://en.wikipedia.org/wiki/Joan_Clarke "clarke", @@ -221,9 +270,24 @@ var ( // Leonardo Da Vinci invented too many things to list here. https://en.wikipedia.org/wiki/Leonardo_da_Vinci. "davinci", + // A. K. (Alexander Keewatin) Dewdney, Canadian mathematician, computer scientist, author and filmmaker. Contributor to Scientific American's "Computer Recreations" from 1984 to 1991. Author of Core War (program), The Planiverse, The Armchair Universe, The Magic Machine, The New Turing Omnibus, and more. https://en.wikipedia.org/wiki/Alexander_Dewdney + "dewdney", + + // Satish Dhawan - Indian mathematician and aerospace engineer, known for leading the successful and indigenous development of the Indian space programme. https://en.wikipedia.org/wiki/Satish_Dhawan + "dhawan", + + // Bailey Whitfield Diffie - American cryptographer and one of the pioneers of public-key cryptography. https://en.wikipedia.org/wiki/Whitfield_Diffie + "diffie", + // Edsger Wybe Dijkstra was a Dutch computer scientist and mathematical scientist. https://en.wikipedia.org/wiki/Edsger_W._Dijkstra. "dijkstra", + // Paul Adrien Maurice Dirac - English theoretical physicist who made fundamental contributions to the early development of both quantum mechanics and quantum electrodynamics. https://en.wikipedia.org/wiki/Paul_Dirac + "dirac", + + // Agnes Meyer Driscoll - American cryptanalyst during World Wars I and II who successfully cryptanalysed a number of Japanese ciphers. She was also the co-developer of one of the cipher machines of the US Navy, the CM. https://en.wikipedia.org/wiki/Agnes_Meyer_Driscoll + "driscoll", + // Donna Dubinsky - played an integral role in the development of personal digital assistants (PDAs) serving as CEO of Palm, Inc. and co-founding Handspring. https://en.wikipedia.org/wiki/Donna_Dubinsky "dubinsky", @@ -236,9 +300,18 @@ var ( // Albert Einstein invented the general theory of relativity. https://en.wikipedia.org/wiki/Albert_Einstein "einstein", + // Alexandra Asanovna Elbakyan (Russian: Алекса́ндра Аса́новна Элбакя́н) is a Kazakhstani graduate student, computer programmer, internet pirate in hiding, and the creator of the site Sci-Hub. Nature has listed her in 2016 in the top ten people that mattered in science, and Ars Technica has compared her to Aaron Swartz. - https://en.wikipedia.org/wiki/Alexandra_Elbakyan + "elbakyan", + + // Taher A. ElGamal - Egyptian cryptographer best known for the ElGamal discrete log cryptosystem and the ElGamal digital signature scheme. https://en.wikipedia.org/wiki/Taher_Elgamal + "elgamal", + // Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - https://en.wikipedia.org/wiki/Gertrude_Elion "elion", + // James Henry Ellis - British engineer and cryptographer employed by the GCHQ. Best known for conceiving for the first time, the idea of public-key cryptography. https://en.wikipedia.org/wiki/James_H._Ellis + "ellis", + // Douglas Engelbart gave the mother of all demos: https://en.wikipedia.org/wiki/Douglas_Engelbart "engelbart", @@ -248,6 +321,12 @@ var ( // Leonhard Euler invented large parts of modern mathematics. https://de.wikipedia.org/wiki/Leonhard_Euler "euler", + // Michael Faraday - British scientist who contributed to the study of electromagnetism and electrochemistry. https://en.wikipedia.org/wiki/Michael_Faraday + "faraday", + + // Horst Feistel - German-born American cryptographer who was one of the earliest non-government researchers to study the design and theory of block ciphers. Co-developer of DES and Lucifer. Feistel networks, a symmetric structure used in the construction of block ciphers are named after him. https://en.wikipedia.org/wiki/Horst_Feistel + "feistel", + // Pierre de Fermat pioneered several aspects of modern mathematics. https://en.wikipedia.org/wiki/Pierre_de_Fermat "fermat", @@ -260,12 +339,27 @@ var ( // Benjamin Franklin is famous for his experiments in electricity and the invention of the lightning rod. "franklin", + // Yuri Alekseyevich Gagarin - Soviet pilot and cosmonaut, best known as the first human to journey into outer space. https://en.wikipedia.org/wiki/Yuri_Gagarin + "gagarin", + // Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. https://en.wikipedia.org/wiki/Galileo_Galilei "galileo", + // Évariste Galois - French mathematician whose work laid the foundations of Galois theory and group theory, two major branches of abstract algebra, and the subfield of Galois connections, all while still in his late teens. https://en.wikipedia.org/wiki/%C3%89variste_Galois + "galois", + + // Kadambini Ganguly - Indian physician, known for being the first South Asian female physician, trained in western medicine, to graduate in South Asia. https://en.wikipedia.org/wiki/Kadambini_Ganguly + "ganguly", + // William Henry "Bill" Gates III is an American business magnate, philanthropist, investor, computer programmer, and inventor. https://en.wikipedia.org/wiki/Bill_Gates "gates", + // Johann Carl Friedrich Gauss - German mathematician who made significant contributions to many fields, including number theory, algebra, statistics, analysis, differential geometry, geodesy, geophysics, mechanics, electrostatics, magnetic fields, astronomy, matrix theory, and optics. https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss + "gauss", + + // Marie-Sophie Germain - French mathematician, physicist and philosopher. Known for her work on elasticity theory, number theory and philosophy. https://en.wikipedia.org/wiki/Sophie_Germain + "germain", + // Adele Goldberg, was one of the designers and developers of the Smalltalk language. https://en.wikipedia.org/wiki/Adele_Goldberg_(computer_scientist) "goldberg", @@ -281,27 +375,51 @@ var ( // Jane Goodall - British primatologist, ethologist, and anthropologist who is considered to be the world's foremost expert on chimpanzees - https://en.wikipedia.org/wiki/Jane_Goodall "goodall", + // Stephen Jay Gould was was an American paleontologist, evolutionary biologist, and historian of science. He is most famous for the theory of punctuated equilibrium - https://en.wikipedia.org/wiki/Stephen_Jay_Gould + "gould", + + // Carolyn Widney Greider - American molecular biologist and joint winner of the 2009 Nobel Prize for Physiology or Medicine for the discovery of telomerase. https://en.wikipedia.org/wiki/Carol_W._Greider + "greider", + + // Alexander Grothendieck - German-born French mathematician who became a leading figure in the creation of modern algebraic geometry. https://en.wikipedia.org/wiki/Alexander_Grothendieck + "grothendieck", + // Lois Haibt - American computer scientist, part of the team at IBM that developed FORTRAN - https://en.wikipedia.org/wiki/Lois_Haibt "haibt", // Margaret Hamilton - Director of the Software Engineering Division of the MIT Instrumentation Laboratory, which developed on-board flight software for the Apollo space program. https://en.wikipedia.org/wiki/Margaret_Hamilton_(scientist) "hamilton", + // Caroline Harriet Haslett - English electrical engineer, electricity industry administrator and champion of women's rights. Co-author of British Standard 1363 that specifies AC power plugs and sockets used across the United Kingdom (which is widely considered as one of the safest designs). https://en.wikipedia.org/wiki/Caroline_Haslett + "haslett", + // Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. https://en.wikipedia.org/wiki/Stephen_Hawking "hawking", + // Martin Edward Hellman - American cryptologist, best known for his invention of public-key cryptography in co-operation with Whitfield Diffie and Ralph Merkle. https://en.wikipedia.org/wiki/Martin_Hellman + "hellman", + // Werner Heisenberg was a founding father of quantum mechanics. https://en.wikipedia.org/wiki/Werner_Heisenberg "heisenberg", // Grete Hermann was a German philosopher noted for her philosophical work on the foundations of quantum mechanics. https://en.wikipedia.org/wiki/Grete_Hermann "hermann", + // Caroline Lucretia Herschel - German astronomer and discoverer of several comets. https://en.wikipedia.org/wiki/Caroline_Herschel + "herschel", + + // Heinrich Rudolf Hertz - German physicist who first conclusively proved the existence of the electromagnetic waves. https://en.wikipedia.org/wiki/Heinrich_Hertz + "hertz", + // Jaroslav Heyrovský was the inventor of the polarographic method, father of the electroanalytical method, and recipient of the Nobel Prize in 1959. His main field of work was polarography. https://en.wikipedia.org/wiki/Jaroslav_Heyrovsk%C3%BD "heyrovsky", // Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. https://en.wikipedia.org/wiki/Dorothy_Hodgkin "hodgkin", + // Douglas R. Hofstadter is an American professor of cognitive science and author of the Pulitzer Prize and American Book Award-winning work Goedel, Escher, Bach: An Eternal Golden Braid in 1979. A mind-bending work which coined Hofstadter's Law: "It always takes longer than you expect, even when you take into account Hofstadter's Law." https://en.wikipedia.org/wiki/Douglas_Hofstadter + "hofstadter", + // Erna Schneider Hoover revolutionized modern communication by inventing a computerized telephone switching method. https://en.wikipedia.org/wiki/Erna_Schneider_Hoover "hoover", @@ -314,6 +432,9 @@ var ( // Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - https://en.wikipedia.org/wiki/Hypatia "hypatia", + // Teruko Ishizaka - Japanese scientist and immunologist who co-discovered the antibody class Immunoglobulin E. https://en.wikipedia.org/wiki/Teruko_Ishizaka + "ishizaka", + // Mary Jackson, American mathematician and aerospace engineer who earned the highest title within NASA's engineering department - https://en.wikipedia.org/wiki/Mary_Jackson_(engineer) "jackson", @@ -338,15 +459,24 @@ var ( // A. P. J. Abdul Kalam - is an Indian scientist aka Missile Man of India for his work on the development of ballistic missile and launch vehicle technology - https://en.wikipedia.org/wiki/A._P._J._Abdul_Kalam "kalam", + // Sergey Petrovich Kapitsa (Russian: Серге́й Петро́вич Капи́ца; 14 February 1928 – 14 August 2012) was a Russian physicist and demographer. He was best known as host of the popular and long-running Russian scientific TV show, Evident, but Incredible. His father was the Nobel laureate Soviet-era physicist Pyotr Kapitsa, and his brother was the geographer and Antarctic explorer Andrey Kapitsa. - https://en.wikipedia.org/wiki/Sergey_Kapitsa + "kapitsa", + // Susan Kare, created the icons and many of the interface elements for the original Apple Macintosh in the 1980s, and was an original employee of NeXT, working as the Creative Director. https://en.wikipedia.org/wiki/Susan_Kare "kare", + // Mstislav Keldysh - a Soviet scientist in the field of mathematics and mechanics, academician of the USSR Academy of Sciences (1946), President of the USSR Academy of Sciences (1961–1975), three times Hero of Socialist Labor (1956, 1961, 1971), fellow of the Royal Society of Edinburgh (1968). https://en.wikipedia.org/wiki/Mstislav_Keldysh + "keldysh", + // Mary Kenneth Keller, Sister Mary Kenneth Keller became the first American woman to earn a PhD in Computer Science in 1965. https://en.wikipedia.org/wiki/Mary_Kenneth_Keller "keller", // Johannes Kepler, German astronomer known for his three laws of planetary motion - https://en.wikipedia.org/wiki/Johannes_Kepler "kepler", + // Omar Khayyam - Persian mathematician, astronomer and poet. Known for his work on the classification and solution of cubic equations, for his contribution to the understanding of Euclid's fifth postulate and for computing the length of a year very accurately. https://en.wikipedia.org/wiki/Omar_Khayyam + "khayyam", + // Har Gobind Khorana - Indian-American biochemist who shared the 1968 Nobel Prize for Physiology - https://en.wikipedia.org/wiki/Har_Gobind_Khorana "khorana", @@ -374,10 +504,16 @@ var ( // Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - https://en.wikipedia.org/wiki/Mary_Leakey "leakey", - // Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt + // Henrietta Swan Leavitt - she was an American astronomer who discovered the relation between the luminosity and the period of Cepheid variable stars. https://en.wikipedia.org/wiki/Henrietta_Swan_Leavitt "leavitt", - //Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin + // Esther Miriam Zimmer Lederberg - American microbiologist and a pioneer of bacterial genetics. https://en.wikipedia.org/wiki/Esther_Lederberg + "lederberg", + + // Inge Lehmann - Danish seismologist and geophysicist. Known for discovering in 1936 that the Earth has a solid inner core inside a molten outer core. https://en.wikipedia.org/wiki/Inge_Lehmann + "lehmann", + + // Daniel Lewin - Mathematician, Akamai co-founder, soldier, 9/11 victim-- Developed optimization techniques for routing traffic on the internet. Died attempting to stop the 9-11 hijackers. https://en.wikipedia.org/wiki/Daniel_Lewin "lewin", // Ruth Lichterman - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Ruth_Teitelbaum @@ -395,6 +531,15 @@ var ( // Mahavira - Ancient Indian mathematician during 9th century AD who discovered basic algebraic identities - https://en.wikipedia.org/wiki/Mah%C4%81v%C4%ABra_(mathematician) "mahavira", + // Lynn Margulis (b. Lynn Petra Alexander) - an American evolutionary theorist and biologist, science author, educator, and popularizer, and was the primary modern proponent for the significance of symbiosis in evolution. - https://en.wikipedia.org/wiki/Lynn_Margulis + "margulis", + + // Yukihiro Matsumoto - Japanese computer scientist and software programmer best known as the chief designer of the Ruby programming language. https://en.wikipedia.org/wiki/Yukihiro_Matsumoto + "matsumoto", + + // James Clerk Maxwell - Scottish physicist, best known for his formulation of electromagnetic theory. https://en.wikipedia.org/wiki/James_Clerk_Maxwell + "maxwell", + // Maria Mayer - American theoretical physicist and Nobel laureate in Physics for proposing the nuclear shell model of the atomic nucleus - https://en.wikipedia.org/wiki/Maria_Mayer "mayer", @@ -404,18 +549,30 @@ var ( // Barbara McClintock - a distinguished American cytogeneticist, 1983 Nobel Laureate in Physiology or Medicine for discovering transposons. https://en.wikipedia.org/wiki/Barbara_McClintock "mcclintock", + // Anne Laura Dorinthea McLaren - British developmental biologist whose work helped lead to human in-vitro fertilisation. https://en.wikipedia.org/wiki/Anne_McLaren + "mclaren", + // Malcolm McLean invented the modern shipping container: https://en.wikipedia.org/wiki/Malcom_McLean "mclean", // Kay McNulty - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Kathleen_Antonelli "mcnulty", + // Gregor Johann Mendel - Czech scientist and founder of genetics. https://en.wikipedia.org/wiki/Gregor_Mendel + "mendel", + + // Dmitri Mendeleev - a chemist and inventor. He formulated the Periodic Law, created a farsighted version of the periodic table of elements, and used it to correct the properties of some already discovered elements and also to predict the properties of eight elements yet to be discovered. https://en.wikipedia.org/wiki/Dmitri_Mendeleev + "mendeleev", + // Lise Meitner - Austrian/Swedish physicist who was involved in the discovery of nuclear fission. The element meitnerium is named after her - https://en.wikipedia.org/wiki/Lise_Meitner "meitner", // Carla Meninsky, was the game designer and programmer for Atari 2600 games Dodge 'Em and Warlords. https://en.wikipedia.org/wiki/Carla_Meninsky "meninsky", + // Ralph C. Merkle - American computer scientist, known for devising Merkle's puzzles - one of the very first schemes for public-key cryptography. Also, inventor of Merkle trees and co-inventor of the Merkle-Damgård construction for building collision-resistant cryptographic hash functions and the Merkle-Hellman knapsack cryptosystem. https://en.wikipedia.org/wiki/Ralph_Merkle + "merkle", + // Johanna Mestorf - German prehistoric archaeologist and first female museum director in Germany - https://en.wikipedia.org/wiki/Johanna_Mestorf "mestorf", @@ -425,12 +582,24 @@ var ( // Maryam Mirzakhani - an Iranian mathematician and the first woman to win the Fields Medal. https://en.wikipedia.org/wiki/Maryam_Mirzakhani "mirzakhani", + // Gordon Earle Moore - American engineer, Silicon Valley founding father, author of Moore's law. https://en.wikipedia.org/wiki/Gordon_Moore + "moore", + // Samuel Morse - contributed to the invention of a single-wire telegraph system based on European telegraphs and was a co-developer of the Morse code - https://en.wikipedia.org/wiki/Samuel_Morse "morse", // Ian Murdock - founder of the Debian project - https://en.wikipedia.org/wiki/Ian_Murdock "murdock", + // May-Britt Moser - Nobel prize winner neuroscientist who contributed to the discovery of grid cells in the brain. https://en.wikipedia.org/wiki/May-Britt_Moser + "moser", + + // John Napier of Merchiston - Scottish landowner known as an astronomer, mathematician and physicist. Best known for his discovery of logarithms. https://en.wikipedia.org/wiki/John_Napier + "napier", + + // John Forbes Nash, Jr. - American mathematician who made fundamental contributions to game theory, differential geometry, and the study of partial differential equations. https://en.wikipedia.org/wiki/John_Forbes_Nash_Jr. + "nash", + // John von Neumann - todays computer architectures are based on the von Neumann architecture. https://en.wikipedia.org/wiki/Von_Neumann_architecture "neumann", @@ -458,6 +627,9 @@ var ( // Ambroise Pare invented modern surgery. https://en.wikipedia.org/wiki/Ambroise_Par%C3%A9 "pare", + // Blaise Pascal, French mathematician, physicist, and inventor - https://en.wikipedia.org/wiki/Blaise_Pascal + "pascal", + // Louis Pasteur discovered vaccination, fermentation and pasteurization. https://en.wikipedia.org/wiki/Louis_Pasteur. "pasteur", @@ -476,6 +648,9 @@ var ( // Laura Poitras is a director and producer whose work, made possible by open source crypto tools, advances the causes of truth and freedom of information by reporting disclosures by whistleblowers such as Edward Snowden. https://en.wikipedia.org/wiki/Laura_Poitras "poitras", + // Tat’yana Avenirovna Proskuriakova (Russian: Татья́на Авени́ровна Проскуряко́ва) (January 23 [O.S. January 10] 1909 – August 30, 1985) was a Russian-American Mayanist scholar and archaeologist who contributed significantly to the deciphering of Maya hieroglyphs, the writing system of the pre-Columbian Maya civilization of Mesoamerica. https://en.wikipedia.org/wiki/Tatiana_Proskouriakoff + "proskuriakova", + // Claudius Ptolemy - a Greco-Egyptian writer of Alexandria, known as a mathematician, astronomer, geographer, astrologer, and poet of a single epigram in the Greek Anthology - https://en.wikipedia.org/wiki/Ptolemy "ptolemy", @@ -494,18 +669,36 @@ var ( // Dennis Ritchie - co-creator of UNIX and the C programming language. - https://en.wikipedia.org/wiki/Dennis_Ritchie "ritchie", + // Ida Rhodes - American pioneer in computer programming, designed the first computer used for Social Security. https://en.wikipedia.org/wiki/Ida_Rhodes + "rhodes", + + // Julia Hall Bowman Robinson - American mathematician renowned for her contributions to the fields of computability theory and computational complexity theory. https://en.wikipedia.org/wiki/Julia_Robinson + "robinson", + // Wilhelm Conrad Röntgen - German physicist who was awarded the first Nobel Prize in Physics in 1901 for the discovery of X-rays (Röntgen rays). https://en.wikipedia.org/wiki/Wilhelm_R%C3%B6ntgen "roentgen", // Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - https://en.wikipedia.org/wiki/Rosalind_Franklin "rosalind", + // Vera Rubin - American astronomer who pioneered work on galaxy rotation rates. https://en.wikipedia.org/wiki/Vera_Rubin + "rubin", + // Meghnad Saha - Indian astrophysicist best known for his development of the Saha equation, used to describe chemical and physical conditions in stars - https://en.wikipedia.org/wiki/Meghnad_Saha "saha", // Jean E. Sammet developed FORMAC, the first widely used computer language for symbolic manipulation of mathematical formulas. https://en.wikipedia.org/wiki/Jean_E._Sammet "sammet", + // Mildred Sanderson - American mathematician best known for Sanderson's theorem concerning modular invariants. https://en.wikipedia.org/wiki/Mildred_Sanderson + "sanderson", + + // Adi Shamir - Israeli cryptographer whose numerous inventions and contributions to cryptography include the Ferge Fiat Shamir identification scheme, the Rivest Shamir Adleman (RSA) public-key cryptosystem, the Shamir's secret sharing scheme, the breaking of the Merkle-Hellman cryptosystem, the TWINKLE and TWIRL factoring devices and the discovery of differential cryptanalysis (with Eli Biham). https://en.wikipedia.org/wiki/Adi_Shamir + "shamir", + + // Claude Shannon - The father of information theory and founder of digital circuit design theory. (https://en.wikipedia.org/wiki/Claude_Shannon) + "shannon", + // Carol Shaw - Originally an Atari employee, Carol Shaw is said to be the first female video game designer. https://en.wikipedia.org/wiki/Carol_Shaw_(video_game_designer) "shaw", @@ -515,12 +708,18 @@ var ( // William Shockley co-invented the transistor - https://en.wikipedia.org/wiki/William_Shockley "shockley", + // Lina Solomonovna Stern (or Shtern; Russian: Лина Соломоновна Штерн; 26 August 1878 – 7 March 1968) was a Soviet biochemist, physiologist and humanist whose medical discoveries saved thousands of lives at the fronts of World War II. She is best known for her pioneering work on blood–brain barrier, which she described as hemato-encephalic barrier in 1921. https://en.wikipedia.org/wiki/Lina_Stern + "shtern", + // Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. https://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi "sinoussi", // Betty Snyder - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Betty_Holberton "snyder", + // Cynthia Solomon - Pioneer in the fields of artificial intelligence, computer science and educational computing. Known for creation of Logo, an educational programming language. https://en.wikipedia.org/wiki/Cynthia_Solomon + "solomon", + // Frances Spence - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Frances_Spence "spence", @@ -530,6 +729,9 @@ var ( // Michael Stonebraker is a database research pioneer and architect of Ingres, Postgres, VoltDB and SciDB. Winner of 2014 ACM Turing Award. https://en.wikipedia.org/wiki/Michael_Stonebraker "stonebraker", + // Ivan Edward Sutherland - American computer scientist and Internet pioneer, widely regarded as the father of computer graphics. https://en.wikipedia.org/wiki/Ivan_Sutherland + "sutherland", + // Janese Swanson (with others) developed the first of the Carmen Sandiego games. She went on to found Girl Tech. https://en.wikipedia.org/wiki/Janese_Swanson "swanson", @@ -539,36 +741,60 @@ var ( // Bertha Swirles was a theoretical physicist who made a number of contributions to early quantum theory. https://en.wikipedia.org/wiki/Bertha_Swirles "swirles", + // Helen Brooke Taussig - American cardiologist and founder of the field of paediatric cardiology. https://en.wikipedia.org/wiki/Helen_B._Taussig + "taussig", + + // Valentina Tereshkova is a Russian engineer, cosmonaut and politician. She was the first woman to fly to space in 1963. In 2013, at the age of 76, she offered to go on a one-way mission to Mars. https://en.wikipedia.org/wiki/Valentina_Tereshkova + "tereshkova", + // Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. https://en.wikipedia.org/wiki/Nikola_Tesla "tesla", + // Marie Tharp - American geologist and oceanic cartographer who co-created the first scientific map of the Atlantic Ocean floor. Her work led to the acceptance of the theories of plate tectonics and continental drift. https://en.wikipedia.org/wiki/Marie_Tharp + "tharp", + // Ken Thompson - co-creator of UNIX and the C programming language - https://en.wikipedia.org/wiki/Ken_Thompson "thompson", // Linus Torvalds invented Linux and Git. https://en.wikipedia.org/wiki/Linus_Torvalds "torvalds", + // Youyou Tu - Chinese pharmaceutical chemist and educator known for discovering artemisinin and dihydroartemisinin, used to treat malaria, which has saved millions of lives. Joint winner of the 2015 Nobel Prize in Physiology or Medicine. https://en.wikipedia.org/wiki/Tu_Youyou + "tu", + // Alan Turing was a founding father of computer science. https://en.wikipedia.org/wiki/Alan_Turing. "turing", // Varahamihira - Ancient Indian mathematician who discovered trigonometric formulae during 505-587 CE - https://en.wikipedia.org/wiki/Var%C4%81hamihira#Contributions "varahamihira", + // Dorothy Vaughan was a NASA mathematician and computer programmer on the SCOUT launch vehicle program that put America's first satellites into space - https://en.wikipedia.org/wiki/Dorothy_Vaughan + "vaughan", + // Sir Mokshagundam Visvesvaraya - is a notable Indian engineer. He is a recipient of the Indian Republic's highest honour, the Bharat Ratna, in 1955. On his birthday, 15 September is celebrated as Engineer's Day in India in his memory - https://en.wikipedia.org/wiki/Visvesvaraya "visvesvaraya", // Christiane Nüsslein-Volhard - German biologist, won Nobel Prize in Physiology or Medicine in 1995 for research on the genetic control of embryonic development. https://en.wikipedia.org/wiki/Christiane_N%C3%BCsslein-Volhard "volhard", + // Cédric Villani - French mathematician, won Fields Medal, Fermat Prize and Poincaré Price for his work in differential geometry and statistical mechanics. https://en.wikipedia.org/wiki/C%C3%A9dric_Villani + "villani", + // Marlyn Wescoff - one of the original programmers of the ENIAC. https://en.wikipedia.org/wiki/ENIAC - https://en.wikipedia.org/wiki/Marlyn_Meltzer "wescoff", + // Sylvia B. Wilbur - British computer scientist who helped develop the ARPANET, was one of the first to exchange email in the UK and a leading researcher in computer-supported collaborative work. https://en.wikipedia.org/wiki/Sylvia_Wilbur + "wilbur", + // Andrew Wiles - Notable British mathematician who proved the enigmatic Fermat's Last Theorem - https://en.wikipedia.org/wiki/Andrew_Wiles "wiles", // Roberta Williams, did pioneering work in graphical adventure games for personal computers, particularly the King's Quest series. https://en.wikipedia.org/wiki/Roberta_Williams "williams", + // Malcolm John Williamson - British mathematician and cryptographer employed by the GCHQ. Developed in 1974 what is now known as Diffie-Hellman key exchange (Diffie and Hellman first published the scheme in 1976). https://en.wikipedia.org/wiki/Malcolm_J._Williamson + "williamson", + // Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. https://en.wikipedia.org/wiki/Sophie_Wilson "wilson", @@ -581,11 +807,17 @@ var ( // The Wright brothers, Orville and Wilbur - credited with inventing and building the world's first successful airplane and making the first controlled, powered and sustained heavier-than-air human flight - https://en.wikipedia.org/wiki/Wright_brothers "wright", + // Chien-Shiung Wu - Chinese-American experimental physicist who made significant contributions to nuclear physics. https://en.wikipedia.org/wiki/Chien-Shiung_Wu + "wu", + // Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. https://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow "yalow", // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath "yonath", + + // Nikolay Yegorovich Zhukovsky (Russian: Никола́й Его́рович Жуко́вский, January 17 1847 – March 17, 1921) was a Russian scientist, mathematician and engineer, and a founding father of modern aero- and hydrodynamics. Whereas contemporary scientists scoffed at the idea of human flight, Zhukovsky was the first to undertake the study of airflow. He is often called the Father of Russian Aviation. https://en.wikipedia.org/wiki/Nikolay_Yegorovich_Zhukovsky + "zhukovsky", } ) diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel.go new file mode 100644 index 000000000..94780ef61 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel.go @@ -0,0 +1,74 @@ +// +build !windows + +// Package kernel provides helper function to get, parse and compare kernel +// versions for different platforms. +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "errors" + "fmt" +) + +// VersionInfo holds information about the kernel. +type VersionInfo struct { + Kernel int // Version of the kernel (e.g. 4.1.2-generic -> 4) + Major int // Major part of the kernel version (e.g. 4.1.2-generic -> 1) + Minor int // Minor part of the kernel version (e.g. 4.1.2-generic -> 2) + Flavor string // Flavor of the kernel version (e.g. 4.1.2-generic -> generic) +} + +func (k *VersionInfo) String() string { + return fmt.Sprintf("%d.%d.%d%s", k.Kernel, k.Major, k.Minor, k.Flavor) +} + +// CompareKernelVersion compares two kernel.VersionInfo structs. +// Returns -1 if a < b, 0 if a == b, 1 it a > b +func CompareKernelVersion(a, b VersionInfo) int { + if a.Kernel < b.Kernel { + return -1 + } else if a.Kernel > b.Kernel { + return 1 + } + + if a.Major < b.Major { + return -1 + } else if a.Major > b.Major { + return 1 + } + + if a.Minor < b.Minor { + return -1 + } else if a.Minor > b.Minor { + return 1 + } + + return 0 +} + +// ParseRelease parses a string and creates a VersionInfo based on it. +func ParseRelease(release string) (*VersionInfo, error) { + var ( + kernel, major, minor, parsed int + flavor, partial string + ) + + // Ignore error from Sscanf to allow an empty flavor. Instead, just + // make sure we got all the version numbers. + parsed, _ = fmt.Sscanf(release, "%d.%d%s", &kernel, &major, &partial) + if parsed < 2 { + return nil, errors.New("Can't parse kernel version " + release) + } + + // sometimes we have 3.12.25-gentoo, but sometimes we just have 3.12-1-amd64 + parsed, _ = fmt.Sscanf(partial, ".%d%s", &minor, &flavor) + if parsed < 1 { + flavor = partial + } + + return &VersionInfo{ + Kernel: kernel, + Major: major, + Minor: minor, + Flavor: flavor, + }, nil +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_darwin.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_darwin.go new file mode 100644 index 000000000..6e599eebc --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_darwin.go @@ -0,0 +1,56 @@ +// +build darwin + +// Package kernel provides helper function to get, parse and compare kernel +// versions for different platforms. +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/mattn/go-shellwords" +) + +// GetKernelVersion gets the current kernel version. +func GetKernelVersion() (*VersionInfo, error) { + release, err := getRelease() + if err != nil { + return nil, err + } + + return ParseRelease(release) +} + +// getRelease uses `system_profiler SPSoftwareDataType` to get OSX kernel version +func getRelease() (string, error) { + cmd := exec.Command("system_profiler", "SPSoftwareDataType") + osName, err := cmd.Output() + if err != nil { + return "", err + } + + var release string + data := strings.Split(string(osName), "\n") + for _, line := range data { + if strings.Contains(line, "Kernel Version") { + // It has the format like ' Kernel Version: Darwin 14.5.0' + content := strings.SplitN(line, ":", 2) + if len(content) != 2 { + return "", fmt.Errorf("Kernel Version is invalid") + } + + prettyNames, err := shellwords.Parse(content[1]) + if err != nil { + return "", fmt.Errorf("Kernel Version is invalid: %s", err.Error()) + } + + if len(prettyNames) != 2 { + return "", fmt.Errorf("Kernel Version needs to be 'Darwin x.x.x' ") + } + release = prettyNames[1] + } + } + + return release, nil +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_unix.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_unix.go new file mode 100644 index 000000000..8a9aa3122 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_unix.go @@ -0,0 +1,35 @@ +// +build linux freebsd openbsd + +// Package kernel provides helper function to get, parse and compare kernel +// versions for different platforms. +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "bytes" + + "github.com/sirupsen/logrus" +) + +// GetKernelVersion gets the current kernel version. +func GetKernelVersion() (*VersionInfo, error) { + uts, err := uname() + if err != nil { + return nil, err + } + + // Remove the \x00 from the release for Atoi to parse correctly + return ParseRelease(string(uts.Release[:bytes.IndexByte(uts.Release[:], 0)])) +} + +// CheckKernelVersion checks if current kernel is newer than (or equal to) +// the given version. +func CheckKernelVersion(k, major, minor int) bool { + if v, err := GetKernelVersion(); err != nil { + logrus.Warnf("error getting kernel version: %s", err) + } else { + if CompareKernelVersion(*v, VersionInfo{Kernel: k, Major: major, Minor: minor}) < 0 { + return false + } + } + return true +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go new file mode 100644 index 000000000..b7b15a1fd --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/kernel_windows.go @@ -0,0 +1,51 @@ +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "fmt" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +// VersionInfo holds information about the kernel. +type VersionInfo struct { + kvi string // Version of the kernel (e.g. 6.1.7601.17592 -> 6) + major int // Major part of the kernel version (e.g. 6.1.7601.17592 -> 1) + minor int // Minor part of the kernel version (e.g. 6.1.7601.17592 -> 7601) + build int // Build number of the kernel version (e.g. 6.1.7601.17592 -> 17592) +} + +func (k *VersionInfo) String() string { + return fmt.Sprintf("%d.%d %d (%s)", k.major, k.minor, k.build, k.kvi) +} + +// GetKernelVersion gets the current kernel version. +func GetKernelVersion() (*VersionInfo, error) { + + KVI := &VersionInfo{"Unknown", 0, 0, 0} + + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return KVI, err + } + defer k.Close() + + blex, _, err := k.GetStringValue("BuildLabEx") + if err != nil { + return KVI, err + } + KVI.kvi = blex + + // Important - docker.exe MUST be manifested for this API to return + // the correct information. + dwVersion, err := windows.GetVersion() + if err != nil { + return KVI, err + } + + KVI.major = int(dwVersion & 0xFF) + KVI.minor = int((dwVersion & 0XFF00) >> 8) + KVI.build = int((dwVersion & 0xFFFF0000) >> 16) + + return KVI, nil +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go new file mode 100644 index 000000000..212ff4502 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_linux.go @@ -0,0 +1,17 @@ +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import "golang.org/x/sys/unix" + +// Utsname represents the system name structure. +// It is passthrough for unix.Utsname in order to make it portable with +// other platforms where it is not available. +type Utsname unix.Utsname + +func uname() (*unix.Utsname, error) { + uts := &unix.Utsname{} + + if err := unix.Uname(uts); err != nil { + return nil, err + } + return uts, nil +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_solaris.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_solaris.go new file mode 100644 index 000000000..b2139b60e --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_solaris.go @@ -0,0 +1,14 @@ +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "golang.org/x/sys/unix" +) + +func uname() (*unix.Utsname, error) { + uts := &unix.Utsname{} + + if err := unix.Uname(uts); err != nil { + return nil, err + } + return uts, nil +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go new file mode 100644 index 000000000..97906e4cd --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/parsers/kernel/uname_unsupported.go @@ -0,0 +1,18 @@ +// +build !linux + +package kernel // import "github.com/docker/docker/pkg/parsers/kernel" + +import ( + "errors" +) + +// Utsname represents the system name structure. +// It is defined here to make it portable as it is available on linux but not +// on windows. +type Utsname struct { + Release [65]byte +} + +func uname() (*Utsname, error) { + return nil, errors.New("Kernel version detection is available only on linux") +} diff --git a/vendor/github.com/docker/docker/pkg/parsers/parsers.go b/vendor/github.com/docker/docker/pkg/parsers/parsers.go index acc897168..068e52480 100644 --- a/vendor/github.com/docker/docker/pkg/parsers/parsers.go +++ b/vendor/github.com/docker/docker/pkg/parsers/parsers.go @@ -1,7 +1,7 @@ // Package parsers provides helper functions to parse and validate different type // of string. It can be hosts, unix addresses, tcp addresses, filters, kernel // operating system versions. -package parsers +package parsers // import "github.com/docker/docker/pkg/parsers" import ( "fmt" @@ -18,6 +18,24 @@ func ParseKeyValueOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } +// ParseUintListMaximum parses and validates the specified string as the value +// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be +// one of the formats below. Note that duplicates are actually allowed in the +// input string. It returns a `map[int]bool` with available elements from `val` +// set to `true`. Values larger than `maximum` cause an error if max is non zero, +// in order to stop the map becoming excessively large. +// Supported formats: +// 7 +// 1-6 +// 0,3-4,7,8-10 +// 0-0,0,1-7 +// 03,1-3 <- this is gonna get parsed as [1,2,3] +// 3,2,1 +// 0-2,3,1 +func ParseUintListMaximum(val string, maximum int) (map[int]bool, error) { + return parseUintList(val, maximum) +} + // ParseUintList parses and validates the specified string as the value // found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be // one of the formats below. Note that duplicates are actually allowed in the @@ -32,6 +50,10 @@ func ParseKeyValueOpt(opt string) (string, string, error) { // 3,2,1 // 0-2,3,1 func ParseUintList(val string) (map[int]bool, error) { + return parseUintList(val, 0) +} + +func parseUintList(val string, maximum int) (map[int]bool, error) { if val == "" { return map[int]bool{}, nil } @@ -46,6 +68,9 @@ func ParseUintList(val string) (map[int]bool, error) { if err != nil { return nil, errInvalidFormat } + if maximum != 0 && v > maximum { + return nil, fmt.Errorf("value of out range, maximum is %d", maximum) + } availableInts[v] = true } else { split := strings.SplitN(r, "-", 2) @@ -60,6 +85,9 @@ func ParseUintList(val string) (map[int]bool, error) { if max < min { return nil, errInvalidFormat } + if maximum != 0 && max > maximum { + return nil, fmt.Errorf("value of out range, maximum is %d", maximum) + } for i := min; i <= max; i++ { availableInts[i] = true } diff --git a/vendor/github.com/docker/docker/pkg/pools/pools.go b/vendor/github.com/docker/docker/pkg/pools/pools.go index 6a111a3ba..46339c282 100644 --- a/vendor/github.com/docker/docker/pkg/pools/pools.go +++ b/vendor/github.com/docker/docker/pkg/pools/pools.go @@ -7,7 +7,7 @@ // // Utility functions which operate on pools should be added to this // package to allow them to be reused. -package pools +package pools // import "github.com/docker/docker/pkg/pools" import ( "bufio" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal.go b/vendor/github.com/docker/docker/pkg/signal/signal.go index 68bb77cf5..88ef7b5ea 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal.go @@ -1,6 +1,6 @@ // Package signal provides helper functions for dealing with signals across // various operating systems. -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "fmt" @@ -13,7 +13,7 @@ import ( // CatchAll catches all signals and relays them to the specified channel. func CatchAll(sigc chan os.Signal) { - handledSigs := []os.Signal{} + var handledSigs []os.Signal for _, s := range SignalMap { handledSigs = append(handledSigs, s) } diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_darwin.go b/vendor/github.com/docker/docker/pkg/signal/signal_darwin.go index 946de87e9..ee5501e3d 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_darwin.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_darwin.go @@ -1,4 +1,4 @@ -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_freebsd.go b/vendor/github.com/docker/docker/pkg/signal/signal_freebsd.go index 6b9569bb7..764f90e26 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_freebsd.go @@ -1,4 +1,4 @@ -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_linux.go b/vendor/github.com/docker/docker/pkg/signal/signal_linux.go index 66c85c8e0..4013bded1 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_linux.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_linux.go @@ -1,4 +1,6 @@ -package signal +// +build !mips,!mipsle,!mips64,!mips64le + +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_linux_mipsx.go b/vendor/github.com/docker/docker/pkg/signal/signal_linux_mipsx.go new file mode 100644 index 000000000..4c7989121 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/signal/signal_linux_mipsx.go @@ -0,0 +1,84 @@ +// +build linux +// +build mips mipsle mips64 mips64le + +package signal // import "github.com/docker/docker/pkg/signal" + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +const ( + sigrtmin = 34 + sigrtmax = 127 +) + +// SignalMap is a map of Linux signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": unix.SIGABRT, + "ALRM": unix.SIGALRM, + "BUS": unix.SIGBUS, + "CHLD": unix.SIGCHLD, + "CLD": unix.SIGCLD, + "CONT": unix.SIGCONT, + "FPE": unix.SIGFPE, + "HUP": unix.SIGHUP, + "ILL": unix.SIGILL, + "INT": unix.SIGINT, + "IO": unix.SIGIO, + "IOT": unix.SIGIOT, + "KILL": unix.SIGKILL, + "PIPE": unix.SIGPIPE, + "POLL": unix.SIGPOLL, + "PROF": unix.SIGPROF, + "PWR": unix.SIGPWR, + "QUIT": unix.SIGQUIT, + "SEGV": unix.SIGSEGV, + "SIGEMT": unix.SIGEMT, + "STOP": unix.SIGSTOP, + "SYS": unix.SIGSYS, + "TERM": unix.SIGTERM, + "TRAP": unix.SIGTRAP, + "TSTP": unix.SIGTSTP, + "TTIN": unix.SIGTTIN, + "TTOU": unix.SIGTTOU, + "URG": unix.SIGURG, + "USR1": unix.SIGUSR1, + "USR2": unix.SIGUSR2, + "VTALRM": unix.SIGVTALRM, + "WINCH": unix.SIGWINCH, + "XCPU": unix.SIGXCPU, + "XFSZ": unix.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_solaris.go b/vendor/github.com/docker/docker/pkg/signal/signal_solaris.go deleted file mode 100644 index 89576b9e3..000000000 --- a/vendor/github.com/docker/docker/pkg/signal/signal_solaris.go +++ /dev/null @@ -1,42 +0,0 @@ -package signal - -import ( - "syscall" -) - -// SignalMap is a map of Solaris signals. -// SIGINFO and SIGTHR not defined for Solaris -var SignalMap = map[string]syscall.Signal{ - "ABRT": syscall.SIGABRT, - "ALRM": syscall.SIGALRM, - "BUF": syscall.SIGBUS, - "CHLD": syscall.SIGCHLD, - "CONT": syscall.SIGCONT, - "EMT": syscall.SIGEMT, - "FPE": syscall.SIGFPE, - "HUP": syscall.SIGHUP, - "ILL": syscall.SIGILL, - "INT": syscall.SIGINT, - "IO": syscall.SIGIO, - "IOT": syscall.SIGIOT, - "KILL": syscall.SIGKILL, - "LWP": syscall.SIGLWP, - "PIPE": syscall.SIGPIPE, - "PROF": syscall.SIGPROF, - "QUIT": syscall.SIGQUIT, - "SEGV": syscall.SIGSEGV, - "STOP": syscall.SIGSTOP, - "SYS": syscall.SIGSYS, - "TERM": syscall.SIGTERM, - "TRAP": syscall.SIGTRAP, - "TSTP": syscall.SIGTSTP, - "TTIN": syscall.SIGTTIN, - "TTOU": syscall.SIGTTOU, - "URG": syscall.SIGURG, - "USR1": syscall.SIGUSR1, - "USR2": syscall.SIGUSR2, - "VTALRM": syscall.SIGVTALRM, - "WINCH": syscall.SIGWINCH, - "XCPU": syscall.SIGXCPU, - "XFSZ": syscall.SIGXFSZ, -} diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_unix.go b/vendor/github.com/docker/docker/pkg/signal/signal_unix.go index 5d058fd56..a2aa4248f 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_unix.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_unix.go @@ -1,6 +1,6 @@ // +build !windows -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_unsupported.go b/vendor/github.com/docker/docker/pkg/signal/signal_unsupported.go index c592d37df..1fd25a83c 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_unsupported.go @@ -1,6 +1,6 @@ -// +build !linux,!darwin,!freebsd,!windows,!solaris +// +build !linux,!darwin,!freebsd,!windows -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/signal_windows.go b/vendor/github.com/docker/docker/pkg/signal/signal_windows.go index 440f2700e..65752f24a 100644 --- a/vendor/github.com/docker/docker/pkg/signal/signal_windows.go +++ b/vendor/github.com/docker/docker/pkg/signal/signal_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/signal/trap.go b/vendor/github.com/docker/docker/pkg/signal/trap.go index 2884dfee3..2a6e69fb5 100644 --- a/vendor/github.com/docker/docker/pkg/signal/trap.go +++ b/vendor/github.com/docker/docker/pkg/signal/trap.go @@ -1,4 +1,4 @@ -package signal +package signal // import "github.com/docker/docker/pkg/signal" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go index a018a203f..8f6e0a737 100644 --- a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go +++ b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go @@ -1,4 +1,4 @@ -package stdcopy +package stdcopy // import "github.com/docker/docker/pkg/stdcopy" import ( "bytes" @@ -21,7 +21,7 @@ const ( // Stderr represents standard error steam type. Stderr // Systemerr represents errors originating from the system that make it - // into the the multiplexed stream. + // into the multiplexed stream. Systemerr stdWriterPrefixLen = 8 diff --git a/vendor/github.com/docker/docker/pkg/stringutils/README.md b/vendor/github.com/docker/docker/pkg/stringutils/README.md deleted file mode 100644 index b3e454573..000000000 --- a/vendor/github.com/docker/docker/pkg/stringutils/README.md +++ /dev/null @@ -1 +0,0 @@ -This package provides helper functions for dealing with strings diff --git a/vendor/github.com/docker/docker/pkg/stringutils/stringutils.go b/vendor/github.com/docker/docker/pkg/stringutils/stringutils.go deleted file mode 100644 index 8c4c39875..000000000 --- a/vendor/github.com/docker/docker/pkg/stringutils/stringutils.go +++ /dev/null @@ -1,99 +0,0 @@ -// Package stringutils provides helper functions for dealing with strings. -package stringutils - -import ( - "bytes" - "math/rand" - "strings" -) - -// GenerateRandomAlphaOnlyString generates an alphabetical random string with length n. -func GenerateRandomAlphaOnlyString(n int) string { - // make a really long string - letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]byte, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] - } - return string(b) -} - -// GenerateRandomASCIIString generates an ASCII random string with length n. -func GenerateRandomASCIIString(n int) string { - chars := "abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` " - res := make([]byte, n) - for i := 0; i < n; i++ { - res[i] = chars[rand.Intn(len(chars))] - } - return string(res) -} - -// Ellipsis truncates a string to fit within maxlen, and appends ellipsis (...). -// For maxlen of 3 and lower, no ellipsis is appended. -func Ellipsis(s string, maxlen int) string { - r := []rune(s) - if len(r) <= maxlen { - return s - } - if maxlen <= 3 { - return string(r[:maxlen]) - } - return string(r[:maxlen-3]) + "..." -} - -// Truncate truncates a string to maxlen. -func Truncate(s string, maxlen int) string { - r := []rune(s) - if len(r) <= maxlen { - return s - } - return string(r[:maxlen]) -} - -// InSlice tests whether a string is contained in a slice of strings or not. -// Comparison is case insensitive -func InSlice(slice []string, s string) bool { - for _, ss := range slice { - if strings.ToLower(s) == strings.ToLower(ss) { - return true - } - } - return false -} - -func quote(word string, buf *bytes.Buffer) { - // Bail out early for "simple" strings - if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { - buf.WriteString(word) - return - } - - buf.WriteString("'") - - for i := 0; i < len(word); i++ { - b := word[i] - if b == '\'' { - // Replace literal ' with a close ', a \', and an open ' - buf.WriteString("'\\''") - } else { - buf.WriteByte(b) - } - } - - buf.WriteString("'") -} - -// ShellQuoteArguments takes a list of strings and escapes them so they will be -// handled right when passed as arguments to a program via a shell -func ShellQuoteArguments(args []string) string { - var buf bytes.Buffer - for i, arg := range args { - if i != 0 { - buf.WriteByte(' ') - } - quote(arg, &buf) - } - return buf.String() -} diff --git a/vendor/github.com/docker/docker/pkg/system/chtimes.go b/vendor/github.com/docker/docker/pkg/system/chtimes.go index 056d19954..c26a4e24b 100644 --- a/vendor/github.com/docker/docker/pkg/system/chtimes.go +++ b/vendor/github.com/docker/docker/pkg/system/chtimes.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "os" @@ -27,9 +27,5 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { } // Take platform specific action for setting create time. - if err := setCTime(name, mtime); err != nil { - return err - } - - return nil + return setCTime(name, mtime) } diff --git a/vendor/github.com/docker/docker/pkg/system/chtimes_unix.go b/vendor/github.com/docker/docker/pkg/system/chtimes_unix.go index 09d58bcbf..259138a45 100644 --- a/vendor/github.com/docker/docker/pkg/system/chtimes_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/chtimes_unix.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( "time" diff --git a/vendor/github.com/docker/docker/pkg/system/chtimes_windows.go b/vendor/github.com/docker/docker/pkg/system/chtimes_windows.go index 45428c141..d3a115ff4 100644 --- a/vendor/github.com/docker/docker/pkg/system/chtimes_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/chtimes_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package system +package system // import "github.com/docker/docker/pkg/system" import ( "time" diff --git a/vendor/github.com/docker/docker/pkg/system/errors.go b/vendor/github.com/docker/docker/pkg/system/errors.go index 288318985..2573d7162 100644 --- a/vendor/github.com/docker/docker/pkg/system/errors.go +++ b/vendor/github.com/docker/docker/pkg/system/errors.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "errors" @@ -7,4 +7,7 @@ import ( var ( // ErrNotSupportedPlatform means the platform is not supported. ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") + + // ErrNotSupportedOperatingSystem means the operating system is not supported. + ErrNotSupportedOperatingSystem = errors.New("operating system is not supported") ) diff --git a/vendor/github.com/docker/docker/pkg/system/exitcode.go b/vendor/github.com/docker/docker/pkg/system/exitcode.go index a5e5616c4..4ba8fe35b 100644 --- a/vendor/github.com/docker/docker/pkg/system/exitcode.go +++ b/vendor/github.com/docker/docker/pkg/system/exitcode.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/system/filesys.go b/vendor/github.com/docker/docker/pkg/system/filesys.go index 102565f76..adeb16305 100644 --- a/vendor/github.com/docker/docker/pkg/system/filesys.go +++ b/vendor/github.com/docker/docker/pkg/system/filesys.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( "io/ioutil" diff --git a/vendor/github.com/docker/docker/pkg/system/filesys_windows.go b/vendor/github.com/docker/docker/pkg/system/filesys_windows.go index a61b53d0b..a1f6013f1 100644 --- a/vendor/github.com/docker/docker/pkg/system/filesys_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/filesys_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package system +package system // import "github.com/docker/docker/pkg/system" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/system/init.go b/vendor/github.com/docker/docker/pkg/system/init.go index 17935088d..a17597aab 100644 --- a/vendor/github.com/docker/docker/pkg/system/init.go +++ b/vendor/github.com/docker/docker/pkg/system/init.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/init_unix.go b/vendor/github.com/docker/docker/pkg/system/init_unix.go index a219895e6..4996a67c1 100644 --- a/vendor/github.com/docker/docker/pkg/system/init_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/init_unix.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" // InitLCOW does nothing since LCOW is a windows only feature func InitLCOW(experimental bool) { diff --git a/vendor/github.com/docker/docker/pkg/system/init_windows.go b/vendor/github.com/docker/docker/pkg/system/init_windows.go index 75f8f2c06..4910ff69d 100644 --- a/vendor/github.com/docker/docker/pkg/system/init_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/init_windows.go @@ -1,17 +1,12 @@ -package system - -import "os" +package system // import "github.com/docker/docker/pkg/system" // lcowSupported determines if Linux Containers on Windows are supported. var lcowSupported = false // InitLCOW sets whether LCOW is supported or not -// TODO @jhowardmsft. -// 1. Replace with RS3 RTM build number. -// 2. Remove the getenv check when image-store is coalesced as shouldn't be needed anymore. func InitLCOW(experimental bool) { v := GetOSVersion() - if experimental && v.Build > 16270 && os.Getenv("LCOW_SUPPORTED") != "" { + if experimental && v.Build >= 16299 { lcowSupported = true } } diff --git a/vendor/github.com/docker/docker/pkg/system/lcow.go b/vendor/github.com/docker/docker/pkg/system/lcow.go index b88c11e31..5be3e2182 100644 --- a/vendor/github.com/docker/docker/pkg/system/lcow.go +++ b/vendor/github.com/docker/docker/pkg/system/lcow.go @@ -1,58 +1,32 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( - "fmt" "runtime" "strings" specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" ) -// ValidatePlatform determines if a platform structure is valid. -// TODO This is a temporary function - can be replaced by parsing from -// https://github.com/containerd/containerd/pull/1403/files at a later date. -// @jhowardmsft -func ValidatePlatform(platform *specs.Platform) error { - platform.Architecture = strings.ToLower(platform.Architecture) - platform.OS = strings.ToLower(platform.OS) - // Based on https://github.com/moby/moby/pull/34642#issuecomment-330375350, do - // not support anything except operating system. - if platform.Architecture != "" { - return fmt.Errorf("invalid platform architecture %q", platform.Architecture) - } - if platform.OS != "" { - if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) { - return fmt.Errorf("invalid platform os %q", platform.OS) - } +// IsOSSupported determines if an operating system is supported by the host +func IsOSSupported(os string) bool { + if strings.EqualFold(runtime.GOOS, os) { + return true } - if len(platform.OSFeatures) != 0 { - return fmt.Errorf("invalid platform osfeatures %q", platform.OSFeatures) + if LCOWSupported() && strings.EqualFold(os, "linux") { + return true } - if platform.OSVersion != "" { - return fmt.Errorf("invalid platform osversion %q", platform.OSVersion) - } - if platform.Variant != "" { - return fmt.Errorf("invalid platform variant %q", platform.Variant) - } - return nil + return false } -// ParsePlatform parses a platform string in the format os[/arch[/variant] -// into an OCI image-spec platform structure. -// TODO This is a temporary function - can be replaced by parsing from -// https://github.com/containerd/containerd/pull/1403/files at a later date. -// @jhowardmsft -func ParsePlatform(in string) *specs.Platform { - p := &specs.Platform{} - elements := strings.SplitN(strings.ToLower(in), "/", 3) - if len(elements) == 3 { - p.Variant = elements[2] - } - if len(elements) >= 2 { - p.Architecture = elements[1] - } - if len(elements) >= 1 { - p.OS = elements[0] +// ValidatePlatform determines if a platform structure is valid. +// TODO This is a temporary windows-only function, should be replaced by +// comparison of worker capabilities +func ValidatePlatform(platform specs.Platform) error { + if runtime.GOOS == "windows" { + if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) { + return errors.Errorf("unsupported os %s", platform.OS) + } } - return p + return nil } diff --git a/vendor/github.com/docker/docker/pkg/system/lcow_unix.go b/vendor/github.com/docker/docker/pkg/system/lcow_unix.go index cff33bb40..26397fb8a 100644 --- a/vendor/github.com/docker/docker/pkg/system/lcow_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/lcow_unix.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" // LCOWSupported returns true if Linux containers on Windows are supported. func LCOWSupported() bool { diff --git a/vendor/github.com/docker/docker/pkg/system/lcow_windows.go b/vendor/github.com/docker/docker/pkg/system/lcow_windows.go index e54d01e69..f0139df8f 100644 --- a/vendor/github.com/docker/docker/pkg/system/lcow_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/lcow_windows.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" // LCOWSupported returns true if Linux containers on Windows are supported. func LCOWSupported() bool { diff --git a/vendor/github.com/docker/docker/pkg/system/lstat_unix.go b/vendor/github.com/docker/docker/pkg/system/lstat_unix.go index bd23c4d50..de5a1c0fb 100644 --- a/vendor/github.com/docker/docker/pkg/system/lstat_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/lstat_unix.go @@ -1,8 +1,9 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( + "os" "syscall" ) @@ -13,7 +14,7 @@ import ( func Lstat(path string) (*StatT, error) { s := &syscall.Stat_t{} if err := syscall.Lstat(path, s); err != nil { - return nil, err + return nil, &os.PathError{Op: "Lstat", Path: path, Err: err} } return fromStatT(s) } diff --git a/vendor/github.com/docker/docker/pkg/system/lstat_windows.go b/vendor/github.com/docker/docker/pkg/system/lstat_windows.go index e51df0daf..359c791d9 100644 --- a/vendor/github.com/docker/docker/pkg/system/lstat_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/lstat_windows.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "os" diff --git a/vendor/github.com/docker/docker/pkg/system/meminfo.go b/vendor/github.com/docker/docker/pkg/system/meminfo.go index 3b6e947e6..6667eb84d 100644 --- a/vendor/github.com/docker/docker/pkg/system/meminfo.go +++ b/vendor/github.com/docker/docker/pkg/system/meminfo.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" // MemInfo contains memory statistics of the host system. type MemInfo struct { diff --git a/vendor/github.com/docker/docker/pkg/system/meminfo_linux.go b/vendor/github.com/docker/docker/pkg/system/meminfo_linux.go index 385f1d5e7..d79e8b076 100644 --- a/vendor/github.com/docker/docker/pkg/system/meminfo_linux.go +++ b/vendor/github.com/docker/docker/pkg/system/meminfo_linux.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "bufio" diff --git a/vendor/github.com/docker/docker/pkg/system/meminfo_solaris.go b/vendor/github.com/docker/docker/pkg/system/meminfo_solaris.go deleted file mode 100644 index 925776e78..000000000 --- a/vendor/github.com/docker/docker/pkg/system/meminfo_solaris.go +++ /dev/null @@ -1,129 +0,0 @@ -// +build solaris,cgo - -package system - -import ( - "fmt" - "unsafe" -) - -// #cgo CFLAGS: -std=c99 -// #cgo LDFLAGS: -lkstat -// #include <unistd.h> -// #include <stdlib.h> -// #include <stdio.h> -// #include <kstat.h> -// #include <sys/swap.h> -// #include <sys/param.h> -// struct swaptable *allocSwaptable(int num) { -// struct swaptable *st; -// struct swapent *swapent; -// st = (struct swaptable *)malloc(num * sizeof(swapent_t) + sizeof (int)); -// swapent = st->swt_ent; -// for (int i = 0; i < num; i++,swapent++) { -// swapent->ste_path = (char *)malloc(MAXPATHLEN * sizeof (char)); -// } -// st->swt_n = num; -// return st; -//} -// void freeSwaptable (struct swaptable *st) { -// struct swapent *swapent = st->swt_ent; -// for (int i = 0; i < st->swt_n; i++,swapent++) { -// free(swapent->ste_path); -// } -// free(st); -// } -// swapent_t getSwapEnt(swapent_t *ent, int i) { -// return ent[i]; -// } -// int64_t getPpKernel() { -// int64_t pp_kernel = 0; -// kstat_ctl_t *ksc; -// kstat_t *ks; -// kstat_named_t *knp; -// kid_t kid; -// -// if ((ksc = kstat_open()) == NULL) { -// return -1; -// } -// if ((ks = kstat_lookup(ksc, "unix", 0, "system_pages")) == NULL) { -// return -1; -// } -// if (((kid = kstat_read(ksc, ks, NULL)) == -1) || -// ((knp = kstat_data_lookup(ks, "pp_kernel")) == NULL)) { -// return -1; -// } -// switch (knp->data_type) { -// case KSTAT_DATA_UINT64: -// pp_kernel = knp->value.ui64; -// break; -// case KSTAT_DATA_UINT32: -// pp_kernel = knp->value.ui32; -// break; -// } -// pp_kernel *= sysconf(_SC_PAGESIZE); -// return (pp_kernel > 0 ? pp_kernel : -1); -// } -import "C" - -// Get the system memory info using sysconf same as prtconf -func getTotalMem() int64 { - pagesize := C.sysconf(C._SC_PAGESIZE) - npages := C.sysconf(C._SC_PHYS_PAGES) - return int64(pagesize * npages) -} - -func getFreeMem() int64 { - pagesize := C.sysconf(C._SC_PAGESIZE) - npages := C.sysconf(C._SC_AVPHYS_PAGES) - return int64(pagesize * npages) -} - -// ReadMemInfo retrieves memory statistics of the host system and returns a -// MemInfo type. -func ReadMemInfo() (*MemInfo, error) { - - ppKernel := C.getPpKernel() - MemTotal := getTotalMem() - MemFree := getFreeMem() - SwapTotal, SwapFree, err := getSysSwap() - - if ppKernel < 0 || MemTotal < 0 || MemFree < 0 || SwapTotal < 0 || - SwapFree < 0 { - return nil, fmt.Errorf("error getting system memory info %v\n", err) - } - - meminfo := &MemInfo{} - // Total memory is total physical memory less than memory locked by kernel - meminfo.MemTotal = MemTotal - int64(ppKernel) - meminfo.MemFree = MemFree - meminfo.SwapTotal = SwapTotal - meminfo.SwapFree = SwapFree - - return meminfo, nil -} - -func getSysSwap() (int64, int64, error) { - var tSwap int64 - var fSwap int64 - var diskblksPerPage int64 - num, err := C.swapctl(C.SC_GETNSWP, nil) - if err != nil { - return -1, -1, err - } - st := C.allocSwaptable(num) - _, err = C.swapctl(C.SC_LIST, unsafe.Pointer(st)) - if err != nil { - C.freeSwaptable(st) - return -1, -1, err - } - - diskblksPerPage = int64(C.sysconf(C._SC_PAGESIZE) >> C.DEV_BSHIFT) - for i := 0; i < int(num); i++ { - swapent := C.getSwapEnt(&st.swt_ent[0], C.int(i)) - tSwap += int64(swapent.ste_pages) * diskblksPerPage - fSwap += int64(swapent.ste_free) * diskblksPerPage - } - C.freeSwaptable(st) - return tSwap, fSwap, nil -} diff --git a/vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go b/vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go index 3ce019dff..56f449426 100644 --- a/vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go @@ -1,6 +1,6 @@ -// +build !linux,!windows,!solaris +// +build !linux,!windows -package system +package system // import "github.com/docker/docker/pkg/system" // ReadMemInfo is not supported on platforms other than linux and windows. func ReadMemInfo() (*MemInfo, error) { diff --git a/vendor/github.com/docker/docker/pkg/system/meminfo_windows.go b/vendor/github.com/docker/docker/pkg/system/meminfo_windows.go index 883944a4c..6ed93f2fe 100644 --- a/vendor/github.com/docker/docker/pkg/system/meminfo_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/meminfo_windows.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "unsafe" diff --git a/vendor/github.com/docker/docker/pkg/system/mknod.go b/vendor/github.com/docker/docker/pkg/system/mknod.go index 2200ec42d..b132482e0 100644 --- a/vendor/github.com/docker/docker/pkg/system/mknod.go +++ b/vendor/github.com/docker/docker/pkg/system/mknod.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/system/mknod_windows.go b/vendor/github.com/docker/docker/pkg/system/mknod_windows.go index 2e863c021..ec89d7a15 100644 --- a/vendor/github.com/docker/docker/pkg/system/mknod_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/mknod_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package system +package system // import "github.com/docker/docker/pkg/system" // Mknod is not implemented on Windows. func Mknod(path string, mode uint32, dev int) error { diff --git a/vendor/github.com/docker/docker/pkg/system/path.go b/vendor/github.com/docker/docker/pkg/system/path.go index 034c33c87..a3d957afa 100644 --- a/vendor/github.com/docker/docker/pkg/system/path.go +++ b/vendor/github.com/docker/docker/pkg/system/path.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "fmt" @@ -35,7 +35,7 @@ func DefaultPathEnv(os string) string { // This is used, for example, when validating a user provided path in docker cp. // If a drive letter is supplied, it must be the system drive. The drive letter // is always removed. Also, it translates it to OS semantics (IOW / to \). We -// need the path in this syntax so that it can ultimately be contatenated with +// need the path in this syntax so that it can ultimately be concatenated with // a Windows long-path which doesn't support drive-letters. Examples: // C: --> Fail // C:\ --> \ diff --git a/vendor/github.com/docker/docker/pkg/system/path_unix.go b/vendor/github.com/docker/docker/pkg/system/path_unix.go new file mode 100644 index 000000000..b0b93196a --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/system/path_unix.go @@ -0,0 +1,10 @@ +// +build !windows + +package system // import "github.com/docker/docker/pkg/system" + +// GetLongPathName converts Windows short pathnames to full pathnames. +// For example C:\Users\ADMIN~1 --> C:\Users\Administrator. +// It is a no-op on non-Windows platforms +func GetLongPathName(path string) (string, error) { + return path, nil +} diff --git a/vendor/github.com/docker/docker/pkg/system/path_windows.go b/vendor/github.com/docker/docker/pkg/system/path_windows.go new file mode 100644 index 000000000..188f2c295 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/system/path_windows.go @@ -0,0 +1,24 @@ +package system // import "github.com/docker/docker/pkg/system" + +import "syscall" + +// GetLongPathName converts Windows short pathnames to full pathnames. +// For example C:\Users\ADMIN~1 --> C:\Users\Administrator. +// It is a no-op on non-Windows platforms +func GetLongPathName(path string) (string, error) { + // See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg + p := syscall.StringToUTF16(path) + b := p // GetLongPathName says we can reuse buffer + n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + if n > uint32(len(b)) { + b = make([]uint16, n) + _, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + } + return syscall.UTF16ToString(b), nil +} diff --git a/vendor/github.com/docker/docker/pkg/system/process_unix.go b/vendor/github.com/docker/docker/pkg/system/process_unix.go index 26c8b42c1..0195a891b 100644 --- a/vendor/github.com/docker/docker/pkg/system/process_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/process_unix.go @@ -1,6 +1,6 @@ -// +build linux freebsd solaris darwin +// +build linux freebsd darwin -package system +package system // import "github.com/docker/docker/pkg/system" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/process_windows.go b/vendor/github.com/docker/docker/pkg/system/process_windows.go new file mode 100644 index 000000000..4e70c97b1 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/system/process_windows.go @@ -0,0 +1,18 @@ +package system // import "github.com/docker/docker/pkg/system" + +import "os" + +// IsProcessAlive returns true if process with a given pid is running. +func IsProcessAlive(pid int) bool { + _, err := os.FindProcess(pid) + + return err == nil +} + +// KillProcess force-stops a process. +func KillProcess(pid int) { + p, err := os.FindProcess(pid) + if err == nil { + p.Kill() + } +} diff --git a/vendor/github.com/docker/docker/pkg/system/rm.go b/vendor/github.com/docker/docker/pkg/system/rm.go index 101b569a5..b31099180 100644 --- a/vendor/github.com/docker/docker/pkg/system/rm.go +++ b/vendor/github.com/docker/docker/pkg/system/rm.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "os" @@ -26,7 +26,7 @@ func EnsureRemoveAll(dir string) error { // track retries exitOnErr := make(map[string]int) - maxRetry := 5 + maxRetry := 50 // Attempt to unmount anything beneath this dir first mount.RecursiveUnmount(dir) @@ -34,7 +34,7 @@ func EnsureRemoveAll(dir string) error { for { err := os.RemoveAll(dir) if err == nil { - return err + return nil } pe, ok := err.(*os.PathError) diff --git a/vendor/github.com/docker/docker/pkg/system/stat_darwin.go b/vendor/github.com/docker/docker/pkg/system/stat_darwin.go index 715f05b93..c1c0ee9f3 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_darwin.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_darwin.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/stat_freebsd.go b/vendor/github.com/docker/docker/pkg/system/stat_freebsd.go index 715f05b93..c1c0ee9f3 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_freebsd.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/stat_linux.go b/vendor/github.com/docker/docker/pkg/system/stat_linux.go index 1939f9518..98c9eb18d 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_linux.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_linux.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/stat_openbsd.go b/vendor/github.com/docker/docker/pkg/system/stat_openbsd.go index b607dea94..756b92d1e 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_openbsd.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_openbsd.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/stat_solaris.go b/vendor/github.com/docker/docker/pkg/system/stat_solaris.go index b607dea94..756b92d1e 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_solaris.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_solaris.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/stat_unix.go b/vendor/github.com/docker/docker/pkg/system/stat_unix.go index 91c7d121c..86bb6dd55 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_unix.go @@ -1,8 +1,9 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( + "os" "syscall" ) @@ -47,6 +48,11 @@ func (s StatT) Mtim() syscall.Timespec { return s.mtim } +// IsDir reports whether s describes a directory. +func (s StatT) IsDir() bool { + return s.mode&syscall.S_IFDIR != 0 +} + // Stat takes a path to a file and returns // a system.StatT type pertaining to that file. // @@ -54,7 +60,7 @@ func (s StatT) Mtim() syscall.Timespec { func Stat(path string) (*StatT, error) { s := &syscall.Stat_t{} if err := syscall.Stat(path, s); err != nil { - return nil, err + return nil, &os.PathError{Op: "Stat", Path: path, Err: err} } return fromStatT(s) } diff --git a/vendor/github.com/docker/docker/pkg/system/stat_windows.go b/vendor/github.com/docker/docker/pkg/system/stat_windows.go index 6c6397268..b2456cb88 100644 --- a/vendor/github.com/docker/docker/pkg/system/stat_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/stat_windows.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/system/syscall_unix.go b/vendor/github.com/docker/docker/pkg/system/syscall_unix.go index 49dbdd378..919a412a7 100644 --- a/vendor/github.com/docker/docker/pkg/system/syscall_unix.go +++ b/vendor/github.com/docker/docker/pkg/system/syscall_unix.go @@ -1,6 +1,6 @@ // +build linux freebsd -package system +package system // import "github.com/docker/docker/pkg/system" import "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/system/syscall_windows.go b/vendor/github.com/docker/docker/pkg/system/syscall_windows.go index 23e9b207c..4ae92fa6c 100644 --- a/vendor/github.com/docker/docker/pkg/system/syscall_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/syscall_windows.go @@ -1,16 +1,63 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( + "fmt" + "syscall" "unsafe" "github.com/sirupsen/logrus" "golang.org/x/sys/windows" ) +const ( + OWNER_SECURITY_INFORMATION = 0x00000001 + GROUP_SECURITY_INFORMATION = 0x00000002 + DACL_SECURITY_INFORMATION = 0x00000004 + SACL_SECURITY_INFORMATION = 0x00000008 + LABEL_SECURITY_INFORMATION = 0x00000010 + ATTRIBUTE_SECURITY_INFORMATION = 0x00000020 + SCOPE_SECURITY_INFORMATION = 0x00000040 + PROCESS_TRUST_LABEL_SECURITY_INFORMATION = 0x00000080 + ACCESS_FILTER_SECURITY_INFORMATION = 0x00000100 + BACKUP_SECURITY_INFORMATION = 0x00010000 + PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 + PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000 + UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 + UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 +) + +const ( + SE_UNKNOWN_OBJECT_TYPE = iota + SE_FILE_OBJECT + SE_SERVICE + SE_PRINTER + SE_REGISTRY_KEY + SE_LMSHARE + SE_KERNEL_OBJECT + SE_WINDOW_OBJECT + SE_DS_OBJECT + SE_DS_OBJECT_ALL + SE_PROVIDER_DEFINED_OBJECT + SE_WMIGUID_OBJECT + SE_REGISTRY_WOW64_32KEY +) + +const ( + SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege" +) + +const ( + ContainerAdministratorSidString = "S-1-5-93-2-1" + ContainerUserSidString = "S-1-5-93-2-2" +) + var ( - ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") - procGetVersionExW = modkernel32.NewProc("GetVersionExW") - procGetProductInfo = modkernel32.NewProc("GetProductInfo") + ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0") + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + procGetVersionExW = modkernel32.NewProc("GetVersionExW") + procGetProductInfo = modkernel32.NewProc("GetProductInfo") + procSetNamedSecurityInfo = modadvapi32.NewProc("SetNamedSecurityInfoW") + procGetSecurityDescriptorDacl = modadvapi32.NewProc("GetSecurityDescriptorDacl") ) // OSVersion is a wrapper for Windows version information @@ -53,6 +100,10 @@ func GetOSVersion() OSVersion { return osv } +func (osv OSVersion) ToString() string { + return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build) +} + // IsWindowsClient returns true if the SKU is client // @engine maintainers - this function should not be removed or modified as it // is used to enforce licensing restrictions on Windows. @@ -120,3 +171,23 @@ func HasWin32KSupport() bool { // APIs. return ntuserApiset.Load() == nil } + +func SetNamedSecurityInfo(objectName *uint16, objectType uint32, securityInformation uint32, sidOwner *windows.SID, sidGroup *windows.SID, dacl *byte, sacl *byte) (result error) { + r0, _, _ := syscall.Syscall9(procSetNamedSecurityInfo.Addr(), 7, uintptr(unsafe.Pointer(objectName)), uintptr(objectType), uintptr(securityInformation), uintptr(unsafe.Pointer(sidOwner)), uintptr(unsafe.Pointer(sidGroup)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(sacl)), 0, 0) + if r0 != 0 { + result = syscall.Errno(r0) + } + return +} + +func GetSecurityDescriptorDacl(securityDescriptor *byte, daclPresent *uint32, dacl **byte, daclDefaulted *uint32) (result error) { + r1, _, e1 := syscall.Syscall6(procGetSecurityDescriptorDacl.Addr(), 4, uintptr(unsafe.Pointer(securityDescriptor)), uintptr(unsafe.Pointer(daclPresent)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(daclDefaulted)), 0, 0) + if r1 == 0 { + if e1 != 0 { + result = syscall.Errno(e1) + } else { + result = syscall.EINVAL + } + } + return +} diff --git a/vendor/github.com/docker/docker/pkg/system/umask.go b/vendor/github.com/docker/docker/pkg/system/umask.go index 5a10eda5a..9912a2bab 100644 --- a/vendor/github.com/docker/docker/pkg/system/umask.go +++ b/vendor/github.com/docker/docker/pkg/system/umask.go @@ -1,6 +1,6 @@ // +build !windows -package system +package system // import "github.com/docker/docker/pkg/system" import ( "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/system/umask_windows.go b/vendor/github.com/docker/docker/pkg/system/umask_windows.go index 13f1de176..fc62388c3 100644 --- a/vendor/github.com/docker/docker/pkg/system/umask_windows.go +++ b/vendor/github.com/docker/docker/pkg/system/umask_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package system +package system // import "github.com/docker/docker/pkg/system" // Umask is not supported on the windows platform. func Umask(newmask int) (oldmask int, err error) { diff --git a/vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go b/vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go index 6a7752437..ed1b9fad5 100644 --- a/vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go +++ b/vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/utimes_linux.go b/vendor/github.com/docker/docker/pkg/system/utimes_linux.go index edc588a63..0afe85458 100644 --- a/vendor/github.com/docker/docker/pkg/system/utimes_linux.go +++ b/vendor/github.com/docker/docker/pkg/system/utimes_linux.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go b/vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go index 139714544..095e072e1 100644 --- a/vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go @@ -1,6 +1,6 @@ // +build !linux,!freebsd -package system +package system // import "github.com/docker/docker/pkg/system" import "syscall" diff --git a/vendor/github.com/docker/docker/pkg/system/xattrs_linux.go b/vendor/github.com/docker/docker/pkg/system/xattrs_linux.go index 98b111be4..66d4895b2 100644 --- a/vendor/github.com/docker/docker/pkg/system/xattrs_linux.go +++ b/vendor/github.com/docker/docker/pkg/system/xattrs_linux.go @@ -1,4 +1,4 @@ -package system +package system // import "github.com/docker/docker/pkg/system" import "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go b/vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go index 0114f2227..d780a90cd 100644 --- a/vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go +++ b/vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go @@ -1,6 +1,6 @@ // +build !linux -package system +package system // import "github.com/docker/docker/pkg/system" // Lgetxattr is not supported on platforms other than linux. func Lgetxattr(path string, attr string) ([]byte, error) { diff --git a/vendor/github.com/docker/docker/pkg/term/ascii.go b/vendor/github.com/docker/docker/pkg/term/ascii.go index 55873c055..87bca8d4a 100644 --- a/vendor/github.com/docker/docker/pkg/term/ascii.go +++ b/vendor/github.com/docker/docker/pkg/term/ascii.go @@ -1,4 +1,4 @@ -package term +package term // import "github.com/docker/docker/pkg/term" import ( "fmt" diff --git a/vendor/github.com/docker/docker/pkg/term/proxy.go b/vendor/github.com/docker/docker/pkg/term/proxy.go index e648eb812..da733e584 100644 --- a/vendor/github.com/docker/docker/pkg/term/proxy.go +++ b/vendor/github.com/docker/docker/pkg/term/proxy.go @@ -1,4 +1,4 @@ -package term +package term // import "github.com/docker/docker/pkg/term" import ( "io" @@ -34,6 +34,10 @@ func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader { func (r *escapeProxy) Read(buf []byte) (int, error) { nr, err := r.r.Read(buf) + if len(r.escapeKeys) == 0 { + return nr, err + } + preserve := func() { // this preserves the original key presses in the passed in buffer nr += r.escapeKeyPos diff --git a/vendor/github.com/docker/docker/pkg/term/tc.go b/vendor/github.com/docker/docker/pkg/term/tc.go index 6d2dfd3a8..01bcaa8ab 100644 --- a/vendor/github.com/docker/docker/pkg/term/tc.go +++ b/vendor/github.com/docker/docker/pkg/term/tc.go @@ -1,7 +1,6 @@ // +build !windows -// +build !solaris !cgo -package term +package term // import "github.com/docker/docker/pkg/term" import ( "syscall" diff --git a/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go b/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go deleted file mode 100644 index 50234affc..000000000 --- a/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go +++ /dev/null @@ -1,65 +0,0 @@ -// +build solaris,cgo - -package term - -import ( - "syscall" - "unsafe" - - "golang.org/x/sys/unix" -) - -// #include <termios.h> -import "C" - -// Termios is the Unix API for terminal I/O. -// It is passthrough for unix.Termios in order to make it portable with -// other platforms where it is not available or handled differently. -type Termios unix.Termios - -// MakeRaw put the terminal connected to the given file descriptor into raw -// mode and returns the previous state of the terminal so that it can be -// restored. -func MakeRaw(fd uintptr) (*State, error) { - var oldState State - if err := tcget(fd, &oldState.termios); err != 0 { - return nil, err - } - - newState := oldState.termios - - newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | unix.IXANY) - newState.Oflag &^= unix.OPOST - newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN) - newState.Cflag &^= (unix.CSIZE | unix.PARENB) - newState.Cflag |= unix.CS8 - - /* - VMIN is the minimum number of characters that needs to be read in non-canonical mode for it to be returned - Since VMIN is overloaded with another element in canonical mode when we switch modes it defaults to 4. It - needs to be explicitly set to 1. - */ - newState.Cc[C.VMIN] = 1 - newState.Cc[C.VTIME] = 0 - - if err := tcset(fd, &newState); err != 0 { - return nil, err - } - return &oldState, nil -} - -func tcget(fd uintptr, p *Termios) syscall.Errno { - ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p))) - if ret != 0 { - return err.(syscall.Errno) - } - return 0 -} - -func tcset(fd uintptr, p *Termios) syscall.Errno { - ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p))) - if ret != 0 { - return err.(syscall.Errno) - } - return 0 -} diff --git a/vendor/github.com/docker/docker/pkg/term/term.go b/vendor/github.com/docker/docker/pkg/term/term.go index 4f59d8d93..0589a9551 100644 --- a/vendor/github.com/docker/docker/pkg/term/term.go +++ b/vendor/github.com/docker/docker/pkg/term/term.go @@ -2,7 +2,7 @@ // Package term provides structures and helper functions to work with // terminal (state, sizes). -package term +package term // import "github.com/docker/docker/pkg/term" import ( "errors" diff --git a/vendor/github.com/docker/docker/pkg/term/term_windows.go b/vendor/github.com/docker/docker/pkg/term/term_windows.go index b6819b342..a3c3db131 100644 --- a/vendor/github.com/docker/docker/pkg/term/term_windows.go +++ b/vendor/github.com/docker/docker/pkg/term/term_windows.go @@ -1,6 +1,4 @@ -// +build windows - -package term +package term // import "github.com/docker/docker/pkg/term" import ( "io" @@ -64,13 +62,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { } } - if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" { - // The ConEmu and ConsoleZ terminals emulate ANSI on output streams well. - emulateStdin = true - emulateStdout = false - emulateStderr = false - } - // Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and // STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as // go-ansiterm hasn't switch to x/sys/windows. diff --git a/vendor/github.com/docker/docker/pkg/term/termios_bsd.go b/vendor/github.com/docker/docker/pkg/term/termios_bsd.go index c47341e87..48b16f520 100644 --- a/vendor/github.com/docker/docker/pkg/term/termios_bsd.go +++ b/vendor/github.com/docker/docker/pkg/term/termios_bsd.go @@ -1,6 +1,6 @@ -// +build darwin freebsd openbsd +// +build darwin freebsd openbsd netbsd -package term +package term // import "github.com/docker/docker/pkg/term" import ( "unsafe" diff --git a/vendor/github.com/docker/docker/pkg/term/termios_linux.go b/vendor/github.com/docker/docker/pkg/term/termios_linux.go index 0f21abcc2..6d4c63fdb 100644 --- a/vendor/github.com/docker/docker/pkg/term/termios_linux.go +++ b/vendor/github.com/docker/docker/pkg/term/termios_linux.go @@ -1,4 +1,4 @@ -package term +package term // import "github.com/docker/docker/pkg/term" import ( "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go b/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go index 29d396318..1d7c452cc 100644 --- a/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go +++ b/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go @@ -1,6 +1,6 @@ // +build windows -package windowsconsole +package windowsconsole // import "github.com/docker/docker/pkg/term/windows" import ( "bytes" diff --git a/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go b/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go index 256577e1f..7799a03fc 100644 --- a/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go +++ b/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go @@ -1,6 +1,6 @@ // +build windows -package windowsconsole +package windowsconsole // import "github.com/docker/docker/pkg/term/windows" import ( "io" diff --git a/vendor/github.com/docker/docker/pkg/term/windows/console.go b/vendor/github.com/docker/docker/pkg/term/windows/console.go index 4bad32ea7..527401975 100644 --- a/vendor/github.com/docker/docker/pkg/term/windows/console.go +++ b/vendor/github.com/docker/docker/pkg/term/windows/console.go @@ -1,6 +1,6 @@ // +build windows -package windowsconsole +package windowsconsole // import "github.com/docker/docker/pkg/term/windows" import ( "os" diff --git a/vendor/github.com/docker/docker/pkg/term/windows/windows.go b/vendor/github.com/docker/docker/pkg/term/windows/windows.go index c02a93a03..3e5593ca6 100644 --- a/vendor/github.com/docker/docker/pkg/term/windows/windows.go +++ b/vendor/github.com/docker/docker/pkg/term/windows/windows.go @@ -2,14 +2,14 @@ // When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create // and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls. -package windowsconsole +package windowsconsole // import "github.com/docker/docker/pkg/term/windows" import ( "io/ioutil" "os" "sync" - ansiterm "github.com/Azure/go-ansiterm" + "github.com/Azure/go-ansiterm" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/docker/docker/pkg/term/winsize.go b/vendor/github.com/docker/docker/pkg/term/winsize.go index 85c4d9d67..a19663ad8 100644 --- a/vendor/github.com/docker/docker/pkg/term/winsize.go +++ b/vendor/github.com/docker/docker/pkg/term/winsize.go @@ -1,6 +1,6 @@ -// +build !solaris,!windows +// +build !windows -package term +package term // import "github.com/docker/docker/pkg/term" import ( "golang.org/x/sys/unix" diff --git a/vendor/github.com/docker/docker/pkg/term/winsize_solaris_cgo.go b/vendor/github.com/docker/docker/pkg/term/winsize_solaris_cgo.go deleted file mode 100644 index 39c1d3207..000000000 --- a/vendor/github.com/docker/docker/pkg/term/winsize_solaris_cgo.go +++ /dev/null @@ -1,42 +0,0 @@ -// +build solaris,cgo - -package term - -import ( - "unsafe" - - "golang.org/x/sys/unix" -) - -/* -#include <unistd.h> -#include <stropts.h> -#include <termios.h> - -// Small wrapper to get rid of variadic args of ioctl() -int my_ioctl(int fd, int cmd, struct winsize *ws) { - return ioctl(fd, cmd, ws); -} -*/ -import "C" - -// GetWinsize returns the window size based on the specified file descriptor. -func GetWinsize(fd uintptr) (*Winsize, error) { - ws := &Winsize{} - ret, err := C.my_ioctl(C.int(fd), C.int(unix.TIOCGWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws))) - // Skip retval = 0 - if ret == 0 { - return ws, nil - } - return ws, err -} - -// SetWinsize tries to set the specified window size for the specified file descriptor. -func SetWinsize(fd uintptr, ws *Winsize) error { - ret, err := C.my_ioctl(C.int(fd), C.int(unix.TIOCSWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws))) - // Skip retval = 0 - if ret == 0 { - return nil - } - return err -} diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go index 90a385948..9f222a6ee 100644 --- a/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp.go @@ -1,6 +1,6 @@ // +build linux -package seccomp +package seccomp // import "github.com/docker/docker/profiles/seccomp" import ( "encoding/json" @@ -8,8 +8,8 @@ import ( "fmt" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/stringutils" - "github.com/opencontainers/runtime-spec/specs-go" + "github.com/docker/docker/pkg/parsers/kernel" + specs "github.com/opencontainers/runtime-spec/specs-go" libseccomp "github.com/seccomp/libseccomp-golang" ) @@ -39,6 +39,17 @@ var nativeToSeccomp = map[string]types.Arch{ "s390x": types.ArchS390X, } +// inSlice tests whether a string is contained in a slice of strings or not. +// Comparison is case sensitive +func inSlice(slice []string, s string) bool { + for _, ss := range slice { + if s == ss { + return true + } + } + return false +} + func setupSeccomp(config *types.Seccomp, rs *specs.Spec) (*specs.LinuxSeccomp, error) { if config == nil { return nil, nil @@ -89,29 +100,43 @@ Loop: // Loop through all syscall blocks and convert them to libcontainer format after filtering them for _, call := range config.Syscalls { if len(call.Excludes.Arches) > 0 { - if stringutils.InSlice(call.Excludes.Arches, arch) { + if inSlice(call.Excludes.Arches, arch) { continue Loop } } if len(call.Excludes.Caps) > 0 { for _, c := range call.Excludes.Caps { - if stringutils.InSlice(rs.Process.Capabilities.Effective, c) { + if inSlice(rs.Process.Capabilities.Bounding, c) { continue Loop } } } + if call.Excludes.MinKernel != "" { + if ok, err := kernelGreaterEqualThan(call.Excludes.MinKernel); err != nil { + return nil, err + } else if ok { + continue Loop + } + } if len(call.Includes.Arches) > 0 { - if !stringutils.InSlice(call.Includes.Arches, arch) { + if !inSlice(call.Includes.Arches, arch) { continue Loop } } if len(call.Includes.Caps) > 0 { for _, c := range call.Includes.Caps { - if !stringutils.InSlice(rs.Process.Capabilities.Effective, c) { + if !inSlice(rs.Process.Capabilities.Bounding, c) { continue Loop } } } + if call.Includes.MinKernel != "" { + if ok, err := kernelGreaterEqualThan(call.Includes.MinKernel); err != nil { + return nil, err + } else if !ok { + continue Loop + } + } if call.Name != "" && len(call.Names) != 0 { return nil, errors.New("'name' and 'names' were specified in the seccomp profile, use either 'name' or 'names'") @@ -148,3 +173,19 @@ func createSpecsSyscall(name string, action types.Action, args []*types.Arg) spe } return newCall } + +var currentKernelVersion *kernel.VersionInfo + +func kernelGreaterEqualThan(v string) (bool, error) { + version, err := kernel.ParseRelease(v) + if err != nil { + return false, err + } + if currentKernelVersion == nil { + currentKernelVersion, err = kernel.GetKernelVersion() + if err != nil { + return false, err + } + } + return kernel.CompareKernelVersion(*version, *currentKernelVersion) <= 0, nil +} diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go index 1b5179c70..077674940 100644 --- a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_default.go @@ -1,6 +1,6 @@ // +build linux,seccomp -package seccomp +package seccomp // import "github.com/docker/docker/profiles/seccomp" import ( "github.com/docker/docker/api/types" @@ -315,13 +315,13 @@ func DefaultProfile() *types.Seccomp { "stat64", "statfs", "statfs64", + "statx", "symlink", "symlinkat", "sync", "sync_file_range", "syncfs", "sysinfo", - "syslog", "tee", "tgkill", "time", @@ -357,6 +357,13 @@ func DefaultProfile() *types.Seccomp { Args: []*types.Arg{}, }, { + Names: []string{"ptrace"}, + Action: types.ActAllow, + Includes: types.Filter{ + MinKernel: "4.8", + }, + }, + { Names: []string{"personality"}, Action: types.ActAllow, Args: []*types.Arg{ @@ -491,6 +498,7 @@ func DefaultProfile() *types.Seccomp { "setdomainname", "sethostname", "setns", + "syslog", "umount", "umount2", "unshare", @@ -629,6 +637,28 @@ func DefaultProfile() *types.Seccomp { Caps: []string{"CAP_SYS_TTY_CONFIG"}, }, }, + { + Names: []string{ + "get_mempolicy", + "mbind", + "set_mempolicy", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYS_NICE"}, + }, + }, + { + Names: []string{ + "syslog", + }, + Action: types.ActAllow, + Args: []*types.Arg{}, + Includes: types.Filter{ + Caps: []string{"CAP_SYSLOG"}, + }, + }, } return &types.Seccomp{ diff --git a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go index 0130effa6..67e06401f 100644 --- a/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go +++ b/vendor/github.com/docker/docker/profiles/seccomp/seccomp_unsupported.go @@ -1,6 +1,6 @@ // +build linux,!seccomp -package seccomp +package seccomp // import "github.com/docker/docker/profiles/seccomp" import ( "github.com/docker/docker/api/types" diff --git a/vendor/github.com/docker/docker/vendor.conf b/vendor/github.com/docker/docker/vendor.conf index ee52979e6..805f89626 100644 --- a/vendor/github.com/docker/docker/vendor.conf +++ b/vendor/github.com/docker/docker/vendor.conf @@ -1,149 +1,161 @@ # the following lines are in sorted order, FYI github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 -github.com/Microsoft/hcsshim v0.6.5 -github.com/Microsoft/go-winio v0.4.5 -github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 +github.com/Microsoft/hcsshim v0.8.6 +github.com/Microsoft/go-winio v0.4.11 github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git -github.com/gorilla/context v1.1 -github.com/gorilla/mux v1.1 -github.com/Microsoft/opengcs v0.3.4 +github.com/golang/gddo 9b12a26f3fbd7397dee4e20939ddca719d840d2a +github.com/gorilla/mux v1.7.0 +github.com/Microsoft/opengcs v0.3.9 github.com/kr/pty 5cf931ef8f github.com/mattn/go-shellwords v1.0.3 -github.com/sirupsen/logrus v1.0.3 +github.com/sirupsen/logrus v1.0.6 github.com/tchap/go-patricia v2.2.6 github.com/vdemeester/shakers 24d7f1d6a71aa5d9cbe7390e4afb66b7eef9e1b3 -golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6 -golang.org/x/sys 8dbc5d05d6edcc104950cc299a1ce6641235bc86 -github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 -github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d -golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756 -github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987 -github.com/pmezard/go-difflib v1.0.0 -github.com/gotestyourself/gotestyourself v1.1.0 +golang.org/x/net a680a1efc54dd51c040b3b5ce4939ea3cf2ea0d1 +golang.org/x/sys 41f3e6584952bb034a481797859f6ab34b6803bd +github.com/docker/go-units 47565b4f722fb6ceae66b95f853feed578a4a51c # v0.3.3 +github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 # v0.4.0 +golang.org/x/text f21a4dfb5e38f5895301dc265a8def02365cc3d0 # v0.3.0 +gotest.tools v2.1.0 +github.com/google/go-cmp v0.2.0 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 -github.com/imdario/mergo 0.2.1 -golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 - -github.com/containerd/continuity 22694c680ee48fb8f50015b44618517e2bde77e8 -github.com/moby/buildkit aaff9d591ef128560018433fe61beb802e149de8 -github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2 +github.com/imdario/mergo v0.3.6 +golang.org/x/sync 1d60e4601c6fd243af51cc01ddf169918a5407ca + +# buildkit +github.com/moby/buildkit 34ff9c2366a878ada7938d2f9ede71741b0a220c +github.com/tonistiigi/fsutil 2862f6bc5ac9b97124e552a5c108230b38a1b0ca +github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746 +github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7 +github.com/google/shlex 6f45313302b9c56850fc17f99e40caebce98c716 +github.com/opentracing-contrib/go-stdlib b1a47cfbdd7543e70e9ef3e73d0802ad306cc1cc +github.com/mitchellh/hashstructure 2bca23e0e452137f789efbc8610126fd8b94f73b #get libnetwork packages -github.com/docker/libnetwork 68f1039f172434709a4550fe92e3e058406c74ce + +# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy.installer accordingly +github.com/docker/libnetwork 1a06131fb8a047d919f7deaf02a4c414d7884b83 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b -github.com/hashicorp/memberlist v0.1.0 +github.com/hashicorp/memberlist 3d8438da9589e7b608a83ffac1ef8211486bcb7c github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 -github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d +github.com/hashicorp/go-sockaddr 6d291a969b86c4b633730bfc6b8b9d64c3aafed9 github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 -github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef +github.com/docker/libkv 458977154600b9f23984d9f4b82e79570b5ae12b github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 -github.com/vishvananda/netlink bd6d5de5ccef2d66b0a26177928d0d8895d7f969 -github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 +github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e + +# When updating, consider updating TOMLV_COMMIT in hack/dockerfile/install/tomlv.installer accordingly +github.com/BurntSushi/toml 3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005 # v0.3.1 github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d -github.com/coreos/etcd v3.2.1 +github.com/coreos/etcd v3.3.9 github.com/coreos/go-semver v0.2.0 -github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 +github.com/ugorji/go v1.1.1 github.com/hashicorp/consul v0.5.2 -github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 -github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 +github.com/miekg/dns v1.0.7 +github.com/ishidawataru/sctp 07191f837fedd2f13d1ec7b5f885f0f3ec54b1cb +go.etcd.io/bbolt v1.3.1-etcd.8 # get graph and distribution packages -github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c -github.com/vbatts/tar-split v0.10.1 -github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb +github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580 +github.com/vbatts/tar-split v0.11.0 +github.com/opencontainers/go-digest v1.0.0-rc1 # get go-zfs packages github.com/mistifyio/go-zfs 22c9b32c84eb0d0c6f4043b6e90fc94073de92fa github.com/pborman/uuid v1.0 -google.golang.org/grpc v1.3.0 - -# When updating, also update RUNC_COMMIT in hack/dockerfile/binaries-commits accordingly -github.com/opencontainers/runc 0351df1c5a66838d0c392b4ac4cf9450de844e2d -github.com/opencontainers/image-spec 372ad780f63454fbbbbcc7cf80e5b90245c13e13 -github.com/opencontainers/runtime-spec v1.0.0 +google.golang.org/grpc v1.12.0 +# The version of runc should match the version that is used by the containerd +# version that is used. If you need to update runc, open a pull request in +# the containerd project first, and update both after that is merged. +# This commit does not need to match RUNC_COMMIT as it is used for helper +# packages but should be newer or equal. +github.com/opencontainers/runc 12f6a991201fdb8f82579582d5e00e28fba06d0a +github.com/opencontainers/runtime-spec 29686dbc5559d93fb1ef402eeda3e35c38d75af4 # v1.0.1-59-g29686db +github.com/opencontainers/image-spec v1.0.1 github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 # libcontainer deps (see src/github.com/opencontainers/runc/Godeps/Godeps.json) -github.com/coreos/go-systemd v4 +github.com/coreos/go-systemd v17 github.com/godbus/dbus v4.0.0 github.com/syndtr/gocapability 2c00daeb6c3b45114c80ac44119e7b8801fdd852 -github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 +github.com/golang/protobuf v1.1.0 # gelf logging driver deps -github.com/Graylog2/go-gelf v2 +github.com/Graylog2/go-gelf 4143646226541087117ff2f83334ea48b3201841 -github.com/fluent/fluent-logger-golang v1.2.1 +github.com/fluent/fluent-logger-golang v1.3.0 # fluent-logger-golang deps github.com/philhofer/fwd 98c11a7a6ec829d672b03833c3d69a7fae1ca972 -github.com/tinylib/msgp 75ee40d2601edf122ef667e2a07d600d4c44490c +github.com/tinylib/msgp 3b556c64540842d4f82967be066a7f7fffc3adad # fsnotify -github.com/fsnotify/fsnotify v1.4.2 +github.com/fsnotify/fsnotify v1.4.7 # awslogs deps -github.com/aws/aws-sdk-go v1.4.22 -github.com/go-ini/ini 060d7da055ba6ec5ea7a31f116332fe5efa04ce0 +github.com/aws/aws-sdk-go v1.12.66 +github.com/go-ini/ini v1.25.4 github.com/jmespath/go-jmespath 0b12d6b521d83fc7f755e7cfc1b1fbdd35a01a74 # logentries github.com/bsphere/le_go 7a984a84b5492ae539b79b62fb4a10afc63c7bcf # gcplogs deps -golang.org/x/oauth2 96382aa079b72d8c014eb0c50f6c223d1e6a2de0 -google.golang.org/api 3cc2e591b550923a2c5f0ab5a803feda924d5823 -cloud.google.com/go 9d965e63e8cceb1b5d7977a202f0fcb8866d6525 -github.com/googleapis/gax-go da06d194a00e19ce00d9011a13931c3f6f6887c7 -google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 +golang.org/x/oauth2 ec22f46f877b4505e0117eeaab541714644fdd28 +google.golang.org/api de943baf05a022a8f921b544b7827bacaba1aed5 +go.opencensus.io v0.11.0 +cloud.google.com/go v0.23.0 +github.com/googleapis/gax-go v2.0.0 +google.golang.org/genproto 694d95ba50e67b2e363f3483057db5d4910c18f9 # containerd -github.com/containerd/containerd 06b9cb35161009dcb7123345749fef02f7cea8e0 -github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 +github.com/containerd/containerd e6b3f5632f50dbc4e9cb6288d911bf4f5e95b18e # v1.2.4 +github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c +github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d +github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 +github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 +github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 +github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 +github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a +github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef # cluster -github.com/docker/swarmkit 872861d2ae46958af7ead1d5fffb092c73afbaf0 -github.com/gogo/protobuf v0.4 -github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a -github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e -golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca -golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb +github.com/docker/swarmkit ebfb0aa1118ebfd35a224d72a5d337ce0addd907 +github.com/gogo/protobuf v1.0.0 +github.com/cloudflare/cfssl 1.3.2 +github.com/fernet/fernet-go 1b2437bc582b3cfbb341ee5a29f8ef5b42912ff2 +github.com/google/certificate-transparency-go v1.0.20 +golang.org/x/crypto 0709b304e793a5edb4a2c0145f281ecdc20838a4 +golang.org/x/time fbb02b2291d28baffd63558aa44b4b56f178d650 github.com/hashicorp/go-memdb cb9a474f84cc5e41b273b20c6927680b2a8776ad -github.com/hashicorp/go-immutable-radix 8e8ed81f8f0bf1bdd829593fdd5c29922c1ea990 -github.com/hashicorp/golang-lru a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 -github.com/coreos/pkg fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8 +github.com/hashicorp/go-immutable-radix 826af9ccf0feeee615d546d69b11f8e98da8c8f1 git://github.com/tonistiigi/go-immutable-radix.git +github.com/hashicorp/golang-lru 0fb14efe8c47ae851c0034ed7a448854d3d34cf3 +github.com/coreos/pkg v3 github.com/pivotal-golang/clock 3fd3c1944c59d9742e1cd333672181cd1a6f9fa0 -github.com/prometheus/client_golang 52437c81da6b127a9925d17eb3a382a2e5fd395e -github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 -github.com/prometheus/client_model fa8ad6fec33561be4280a8f0514318c79d7f6cb6 -github.com/prometheus/common ebdfc6da46522d58825777cf1f90490a5b1ef1d8 -github.com/prometheus/procfs abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +github.com/prometheus/client_golang v0.8.0 +github.com/beorn7/perks 3a771d992973f24aa725d07868b467d1ddfceaf +github.com/prometheus/client_model 6f3806018612930941127f2a7c6c453ba2c527d2 +github.com/prometheus/common 7600349dcfe1abd18d72d3a1770870d9800a7801 +github.com/prometheus/procfs 7d6f385de8bea29190f15ba9931442a0eaef9af7 github.com/matttproud/golang_protobuf_extensions v1.0.0 -github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 -github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0 +github.com/pkg/errors 645ef00459ed84a119197bfb8d8205042c6df63d # v0.8.0 +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 # cli -github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git -github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7 -github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty +github.com/spf13/cobra v0.0.3 +github.com/spf13/pflag v1.0.1 +github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 # v1.0 +github.com/morikuni/aec 39771216ff4c63d11f5e604076f9c45e8be1067b # metrics github.com/docker/go-metrics d466d4f6fd960e01820085bd7e1a24426ee7ef18 -github.com/opencontainers/selinux v1.0.0-rc1 - -# archive/tar -# mkdir -p ./vendor/archive -# git clone git://github.com/tonistiigi/go-1.git ./go -# git --git-dir ./go/.git --work-tree ./go checkout revert-prefix-ignore -# cp -a go/src/archive/tar ./vendor/archive/tar -# rm -rf ./go -# vndr +github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a diff --git a/vendor/github.com/docker/libnetwork/LICENSE b/vendor/github.com/docker/libnetwork/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/vendor/github.com/docker/libnetwork/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/vendor/github.com/docker/libnetwork/README.md b/vendor/github.com/docker/libnetwork/README.md new file mode 100644 index 000000000..a9020381a --- /dev/null +++ b/vendor/github.com/docker/libnetwork/README.md @@ -0,0 +1,100 @@ +# libnetwork - networking for containers + +[![Circle CI](https://circleci.com/gh/docker/libnetwork/tree/master.svg?style=svg)](https://circleci.com/gh/docker/libnetwork/tree/master) [![Coverage Status](https://coveralls.io/repos/docker/libnetwork/badge.svg)](https://coveralls.io/r/docker/libnetwork) [![GoDoc](https://godoc.org/github.com/docker/libnetwork?status.svg)](https://godoc.org/github.com/docker/libnetwork) [![Go Report Card](https://goreportcard.com/badge/github.com/docker/libnetwork)](https://goreportcard.com/report/github.com/docker/libnetwork) + +Libnetwork provides a native Go implementation for connecting containers + +The goal of libnetwork is to deliver a robust Container Network Model that provides a consistent programming interface and the required network abstractions for applications. + +#### Design +Please refer to the [design](docs/design.md) for more information. + +#### Using libnetwork + +There are many networking solutions available to suit a broad range of use-cases. libnetwork uses a driver / plugin model to support all of these solutions while abstracting the complexity of the driver implementations by exposing a simple and consistent Network Model to users. + + +```go +import ( + "fmt" + "log" + + "github.com/docker/docker/pkg/reexec" + "github.com/docker/libnetwork" + "github.com/docker/libnetwork/config" + "github.com/docker/libnetwork/netlabel" + "github.com/docker/libnetwork/options" +) + +func main() { + if reexec.Init() { + return + } + + // Select and configure the network driver + networkType := "bridge" + + // Create a new controller instance + driverOptions := options.Generic{} + genericOption := make(map[string]interface{}) + genericOption[netlabel.GenericData] = driverOptions + controller, err := libnetwork.New(config.OptionDriverConfig(networkType, genericOption)) + if err != nil { + log.Fatalf("libnetwork.New: %s", err) + } + + // Create a network for containers to join. + // NewNetwork accepts Variadic optional arguments that libnetwork and Drivers can use. + network, err := controller.NewNetwork(networkType, "network1", "") + if err != nil { + log.Fatalf("controller.NewNetwork: %s", err) + } + + // For each new container: allocate IP and interfaces. The returned network + // settings will be used for container infos (inspect and such), as well as + // iptables rules for port publishing. This info is contained or accessible + // from the returned endpoint. + ep, err := network.CreateEndpoint("Endpoint1") + if err != nil { + log.Fatalf("network.CreateEndpoint: %s", err) + } + + // Create the sandbox for the container. + // NewSandbox accepts Variadic optional arguments which libnetwork can use. + sbx, err := controller.NewSandbox("container1", + libnetwork.OptionHostname("test"), + libnetwork.OptionDomainname("docker.io")) + if err != nil { + log.Fatalf("controller.NewSandbox: %s", err) + } + + // A sandbox can join the endpoint via the join api. + err = ep.Join(sbx) + if err != nil { + log.Fatalf("ep.Join: %s", err) + } + + // libnetwork client can check the endpoint's operational data via the Info() API + epInfo, err := ep.DriverInfo() + if err != nil { + log.Fatalf("ep.DriverInfo: %s", err) + } + + macAddress, ok := epInfo[netlabel.MacAddress] + if !ok { + log.Fatalf("failed to get mac address from endpoint info") + } + + fmt.Printf("Joined endpoint %s (%s) to sandbox %s (%s)\n", ep.Name(), macAddress, sbx.ContainerID(), sbx.Key()) +} +``` + +## Future +Please refer to [roadmap](ROADMAP.md) for more information. + +## Contributing + +Want to hack on libnetwork? [Docker's contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md) apply. + +## Copyright and license +Code and documentation copyright 2015 Docker, inc. Code released under the Apache 2.0 license. Docs released under Creative commons. diff --git a/vendor/github.com/docker/libnetwork/ipamutils/utils.go b/vendor/github.com/docker/libnetwork/ipamutils/utils.go new file mode 100644 index 000000000..3fd37cd88 --- /dev/null +++ b/vendor/github.com/docker/libnetwork/ipamutils/utils.go @@ -0,0 +1,135 @@ +// Package ipamutils provides utility functions for ipam management +package ipamutils + +import ( + "fmt" + "net" + "sync" +) + +var ( + // PredefinedLocalScopeDefaultNetworks contains a list of 31 IPv4 private networks with host size 16 and 12 + // (172.17-31.x.x/16, 192.168.x.x/20) which do not overlap with the networks in `PredefinedGlobalScopeDefaultNetworks` + PredefinedLocalScopeDefaultNetworks []*net.IPNet + // PredefinedGlobalScopeDefaultNetworks contains a list of 64K IPv4 private networks with host size 8 + // (10.x.x.x/24) which do not overlap with the networks in `PredefinedLocalScopeDefaultNetworks` + PredefinedGlobalScopeDefaultNetworks []*net.IPNet + mutex sync.Mutex + localScopeDefaultNetworks = []*NetworkToSplit{{"172.17.0.0/16", 16}, {"172.18.0.0/16", 16}, {"172.19.0.0/16", 16}, + {"172.20.0.0/14", 16}, {"172.24.0.0/14", 16}, {"172.28.0.0/14", 16}, + {"192.168.0.0/16", 20}} + globalScopeDefaultNetworks = []*NetworkToSplit{{"10.0.0.0/8", 24}} +) + +// NetworkToSplit represent a network that has to be split in chunks with mask length Size. +// Each subnet in the set is derived from the Base pool. Base is to be passed +// in CIDR format. +// Example: a Base "10.10.0.0/16 with Size 24 will define the set of 256 +// 10.10.[0-255].0/24 address pools +type NetworkToSplit struct { + Base string `json:"base"` + Size int `json:"size"` +} + +func init() { + var err error + if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(globalScopeDefaultNetworks); err != nil { + //we are going to panic in case of error as we should never get into this state + panic("InitAddressPools failed to initialize the global scope default address pool") + } + + if PredefinedLocalScopeDefaultNetworks, err = splitNetworks(localScopeDefaultNetworks); err != nil { + //we are going to panic in case of error as we should never get into this state + panic("InitAddressPools failed to initialize the local scope default address pool") + } +} + +// configDefaultNetworks configures local as well global default pool based on input +func configDefaultNetworks(defaultAddressPool []*NetworkToSplit, result *[]*net.IPNet) error { + mutex.Lock() + defer mutex.Unlock() + defaultNetworks, err := splitNetworks(defaultAddressPool) + if err != nil { + return err + } + *result = defaultNetworks + return nil +} + +// GetGlobalScopeDefaultNetworks returns PredefinedGlobalScopeDefaultNetworks +func GetGlobalScopeDefaultNetworks() []*net.IPNet { + mutex.Lock() + defer mutex.Unlock() + return PredefinedGlobalScopeDefaultNetworks +} + +// GetLocalScopeDefaultNetworks returns PredefinedLocalScopeDefaultNetworks +func GetLocalScopeDefaultNetworks() []*net.IPNet { + mutex.Lock() + defer mutex.Unlock() + return PredefinedLocalScopeDefaultNetworks +} + +// ConfigGlobalScopeDefaultNetworks configures global default pool. +// Ideally this will be called from SwarmKit as part of swarm init +func ConfigGlobalScopeDefaultNetworks(defaultAddressPool []*NetworkToSplit) error { + if defaultAddressPool == nil { + defaultAddressPool = globalScopeDefaultNetworks + } + return configDefaultNetworks(defaultAddressPool, &PredefinedGlobalScopeDefaultNetworks) +} + +// ConfigLocalScopeDefaultNetworks configures local default pool. +// Ideally this will be called during libnetwork init +func ConfigLocalScopeDefaultNetworks(defaultAddressPool []*NetworkToSplit) error { + if defaultAddressPool == nil { + return nil + } + return configDefaultNetworks(defaultAddressPool, &PredefinedLocalScopeDefaultNetworks) +} + +// splitNetworks takes a slice of networks, split them accordingly and returns them +func splitNetworks(list []*NetworkToSplit) ([]*net.IPNet, error) { + localPools := make([]*net.IPNet, 0, len(list)) + + for _, p := range list { + _, b, err := net.ParseCIDR(p.Base) + if err != nil { + return nil, fmt.Errorf("invalid base pool %q: %v", p.Base, err) + } + ones, _ := b.Mask.Size() + if p.Size <= 0 || p.Size < ones { + return nil, fmt.Errorf("invalid pools size: %d", p.Size) + } + localPools = append(localPools, splitNetwork(p.Size, b)...) + } + return localPools, nil +} + +func splitNetwork(size int, base *net.IPNet) []*net.IPNet { + one, bits := base.Mask.Size() + mask := net.CIDRMask(size, bits) + n := 1 << uint(size-one) + s := uint(bits - size) + list := make([]*net.IPNet, 0, n) + + for i := 0; i < n; i++ { + ip := copyIP(base.IP) + addIntToIP(ip, uint(i<<s)) + list = append(list, &net.IPNet{IP: ip, Mask: mask}) + } + return list +} + +func copyIP(from net.IP) net.IP { + ip := make([]byte, len(from)) + copy(ip, from) + return ip +} + +func addIntToIP(array net.IP, ordinal uint) { + for i := len(array) - 1; i >= 0; i-- { + array[i] |= (byte)(ordinal & 0xff) + ordinal >>= 8 + } +} diff --git a/vendor/github.com/docker/libnetwork/vendor.conf b/vendor/github.com/docker/libnetwork/vendor.conf new file mode 100644 index 000000000..865704ab4 --- /dev/null +++ b/vendor/github.com/docker/libnetwork/vendor.conf @@ -0,0 +1,50 @@ +github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 +github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 +github.com/Microsoft/go-winio v0.4.11 +github.com/Microsoft/hcsshim v0.7.3 +github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec +github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 +github.com/codegangsta/cli a65b733b303f0055f8d324d805f393cd3e7a7904 +github.com/containerd/continuity d3c23511c1bf5851696cba83143d9cbcd666869b +github.com/coreos/etcd v3.2.1 +github.com/coreos/go-semver v0.2.0 +github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d +go.etcd.io/bbolt v1.3.1-etcd.8 + +github.com/docker/docker 162ba6016def672690ee4a1f3978368853a1e149 +github.com/docker/go-connections 7beb39f0b969b075d1325fecb092faf27fd357b6 +github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 +github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 +github.com/docker/libkv 458977154600b9f23984d9f4b82e79570b5ae12b + +github.com/godbus/dbus v4.0.0 +github.com/gogo/protobuf v1.0.0 +github.com/gorilla/context v1.1 +github.com/gorilla/mux v1.1 +github.com/hashicorp/consul v0.5.2 +github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b +github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e +github.com/hashicorp/memberlist 3d8438da9589e7b608a83ffac1ef8211486bcb7c +github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372 +github.com/hashicorp/go-sockaddr 6d291a969b86c4b633730bfc6b8b9d64c3aafed9 +github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 +github.com/mattn/go-shellwords v1.0.3 +github.com/miekg/dns v1.0.7 +github.com/opencontainers/go-digest v1.0.0-rc1 +github.com/opencontainers/image-spec v1.0.1 +github.com/opencontainers/runc 96ec2177ae841256168fcf76954f7177af9446eb +github.com/opencontainers/runtime-spec v1.0.1 +github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 +github.com/sirupsen/logrus v1.0.3 +github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 +github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e +github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25 +golang.org/x/crypto 1a580b3eff7814fc9b40602fd35256c63b50f491 +golang.org/x/net 0ed95abb35c445290478a5348a7b38bb154135fd +golang.org/x/sys 37707fdb30a5b38865cfb95e5aab41707daec7fd +golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5 +github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 +github.com/ishidawataru/sctp 07191f837fedd2f13d1ec7b5f885f0f3ec54b1cb + +gotest.tools v2.1.0 +github.com/google/go-cmp v0.2.0 diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go index acb3a02be..4335d6e06 100644 --- a/vendor/github.com/fsouza/go-dockerclient/auth.go +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -32,6 +32,9 @@ type AuthConfiguration struct { // see https://godoc.org/github.com/docker/docker/api/types#AuthConfig // It can be used in place of password not in conjunction with it IdentityToken string `json:"identitytoken,omitempty"` + + // RegistryToken can be supplied with the registrytoken + RegistryToken string `json:"registrytoken,omitempty"` } // AuthConfigurations represents authentication options to use for the @@ -50,6 +53,7 @@ type dockerConfig struct { Auth string `json:"auth"` Email string `json:"email"` IdentityToken string `json:"identitytoken"` + RegistryToken string `json:"registrytoken"` } // NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON @@ -162,6 +166,11 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { authConfig.IdentityToken = conf.IdentityToken } + // if registrytoken provided then zero the password and set it + if conf.RegistryToken != "" { + authConfig.Password = "" + authConfig.RegistryToken = conf.RegistryToken + } c.Configs[reg] = authConfig } diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go index a13ee7cca..7d7cf496a 100644 --- a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive.go @@ -71,7 +71,7 @@ type TarOptions struct { NoLchown bool UIDMaps []idtools.IDMap GIDMaps []idtools.IDMap - ChownOpts *idtools.IDPair + ChownOpts *idtools.Identity IncludeSourceDir bool // WhiteoutFormat is the expected on disk format for whiteout files. // This format will be converted to the standard format on pack @@ -292,9 +292,9 @@ type tarAppender struct { Buffer *bufio.Writer // for hardlink mapping - SeenFiles map[uint64]string - IDMappings *idtools.IDMappings - ChownOpts *idtools.IDPair + SeenFiles map[uint64]string + IdentityMapping *idtools.IdentityMapping + ChownOpts *idtools.Identity // For packing and unpacking whiteout files in the // non standard format. The whiteout files defined @@ -303,13 +303,13 @@ type tarAppender struct { WhiteoutConverter tarWhiteoutConverter } -func newTarAppender(idMapping *idtools.IDMappings, writer io.Writer, chownOpts *idtools.IDPair) *tarAppender { +func newTarAppender(idMapping *idtools.IdentityMapping, writer io.Writer, chownOpts *idtools.Identity) *tarAppender { return &tarAppender{ - SeenFiles: make(map[uint64]string), - TarWriter: tar.NewWriter(writer), - Buffer: pools.BufioWriter32KPool.Get(nil), - IDMappings: idMapping, - ChownOpts: chownOpts, + SeenFiles: make(map[uint64]string), + TarWriter: tar.NewWriter(writer), + Buffer: pools.BufioWriter32KPool.Get(nil), + IdentityMapping: idMapping, + ChownOpts: chownOpts, } } @@ -364,12 +364,12 @@ func (ta *tarAppender) addTarFile(path, name string) error { //by the kernel and already have proper ownership relative to the host if !isOverlayWhiteout && !strings.HasPrefix(filepath.Base(hdr.Name), WhiteoutPrefix) && - !ta.IDMappings.Empty() { - fileIDPair, err := getFileUIDGID(fi.Sys()) + !ta.IdentityMapping.Empty() { + fileIdentity, err := getFileIdentity(fi.Sys()) if err != nil { return err } - hdr.Uid, hdr.Gid, err = ta.IDMappings.ToContainer(fileIDPair) + hdr.Uid, hdr.Gid, err = ta.IdentityMapping.ToContainer(fileIdentity) if err != nil { return err } diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go index 2633f5020..80199d513 100644 --- a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_unix.go @@ -48,13 +48,13 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) { return } -func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { +func getFileIdentity(stat interface{}) (idtools.Identity, error) { s, ok := stat.(*syscall.Stat_t) if !ok { - return idtools.IDPair{}, errors.New("cannot convert stat value to syscall.Stat_t") + return idtools.Identity{}, errors.New("cannot convert stat value to syscall.Stat_t") } - return idtools.IDPair{UID: int(s.Uid), GID: int(s.Gid)}, nil + return idtools.Identity{UID: int(s.Uid), GID: int(s.Gid)}, nil } func chmodTarEntry(perm os.FileMode) os.FileMode { diff --git a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go index c14875cd7..c47768e68 100644 --- a/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go +++ b/vendor/github.com/fsouza/go-dockerclient/internal/archive/archive_windows.go @@ -47,9 +47,9 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) { return } -func getFileUIDGID(stat interface{}) (idtools.IDPair, error) { +func getFileIdentity(stat interface{}) (idtools.Identity, error) { // no notion of file ownership mapping yet on Windows - return idtools.IDPair{0, 0}, nil + return idtools.Identity{}, nil } // chmodTarEntry is used to adjust the file permissions used in tar header based diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go index c6ddb22c6..8c03b9ae6 100644 --- a/vendor/github.com/fsouza/go-dockerclient/network.go +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "net/http" + "net/url" ) // ErrNetworkAlreadyExists is the error returned by CreateNetwork when the @@ -71,7 +72,9 @@ func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) if err != nil { return nil, err } - path := "/networks?filters=" + string(params) + qs := make(url.Values) + qs.Add("filters", string(params)) + path := "/networks?" + qs.Encode() resp, err := c.do("GET", path, doOptions{}) if err != nil { return nil, err diff --git a/vendor/github.com/fsouza/go-dockerclient/tls.go b/vendor/github.com/fsouza/go-dockerclient/tls.go index bb5790b5f..5f0e2e31e 100644 --- a/vendor/github.com/fsouza/go-dockerclient/tls.go +++ b/vendor/github.com/fsouza/go-dockerclient/tls.go @@ -109,10 +109,10 @@ func copyTLSConfig(cfg *tls.Config) *tls.Config { NameToCertificate: cfg.NameToCertificate, NextProtos: cfg.NextProtos, PreferServerCipherSuites: cfg.PreferServerCipherSuites, - Rand: cfg.Rand, - RootCAs: cfg.RootCAs, - ServerName: cfg.ServerName, - SessionTicketKey: cfg.SessionTicketKey, - SessionTicketsDisabled: cfg.SessionTicketsDisabled, + Rand: cfg.Rand, + RootCAs: cfg.RootCAs, + ServerName: cfg.ServerName, + SessionTicketKey: cfg.SessionTicketKey, + SessionTicketsDisabled: cfg.SessionTicketsDisabled, } } diff --git a/vendor/github.com/openshift/imagebuilder/README.md b/vendor/github.com/openshift/imagebuilder/README.md index f26b4a7e0..fd96ed940 100644 --- a/vendor/github.com/openshift/imagebuilder/README.md +++ b/vendor/github.com/openshift/imagebuilder/README.md @@ -1,4 +1,4 @@ -Docker / OCI Image Builder +OCI Image Builder ========================== [![Go Report Card](https://goreportcard.com/badge/github.com/openshift/imagebuilder)](https://goreportcard.com/report/github.com/openshift/imagebuilder) @@ -6,22 +6,22 @@ Docker / OCI Image Builder [![Travis](https://travis-ci.org/openshift/imagebuilder.svg?branch=master)](https://travis-ci.org/openshift/imagebuilder) [![Join the chat at freenode:openshift-dev](https://img.shields.io/badge/irc-freenode%3A%20%23openshift--dev-blue.svg)](http://webchat.freenode.net/?channels=%23openshift-dev) -Note: this library is beta and may contain bugs that prevent images from being identical to Docker build. Test your images (and add to our conformance suite)! +Please test your images (and add to our conformance suite)! -This library supports using the Dockerfile syntax to build Docker -compatible images, without invoking Docker build. It is intended to give -clients more control over how a Docker build is run, including: +This library supports using the Dockerfile syntax to build OCI & Docker +compatible images, without invoking a container build command such as `buildah bud` or `docker build`. It is intended to give +clients more control over how they build container images, including: * Instead of building one layer per line, run all instructions in the same container -* Set Docker HostConfig settings like network and memory controls that - are not available when running Docker builds +* Set HostConfig settings like network and memory controls that + are not available when running container builds * Mount external files into the build that are not persisted as part of the final image (i.e. "secrets") * If there are no RUN commands in the Dockerfile, the container is created and committed, but never started. -The final image should be 99.9% compatible with regular docker builds, +The final image should be 99.9% compatible with regular container builds, but bugs are always possible. Future goals include: @@ -54,9 +54,6 @@ $ imagebuilder --mount ~/secrets/private.key:/etc/keys/private.key path/to/my/co Any processes in the Dockerfile will have access to `/etc/keys/private.key`, but that file will not be part of the committed image. -Running `--mount` requires Docker 1.10 or newer, as it uses a Docker volume to hold the mounted files and the volume API was not -available in earlier versions. - You can also customize which Dockerfile is run, or run multiple Dockerfiles in sequence (the FROM is ignored on later files): diff --git a/vendor/github.com/openshift/imagebuilder/builder.go b/vendor/github.com/openshift/imagebuilder/builder.go index 16682af7d..86b139b65 100644 --- a/vendor/github.com/openshift/imagebuilder/builder.go +++ b/vendor/github.com/openshift/imagebuilder/builder.go @@ -13,8 +13,8 @@ import ( docker "github.com/fsouza/go-dockerclient" - "github.com/docker/docker/builder/dockerfile/command" - "github.com/docker/docker/builder/dockerfile/parser" + "github.com/openshift/imagebuilder/dockerfile/command" + "github.com/openshift/imagebuilder/dockerfile/parser" ) // Copy defines a copy operation required on the container. diff --git a/vendor/github.com/openshift/imagebuilder/dockerfile/NOTICE b/vendor/github.com/openshift/imagebuilder/dockerfile/NOTICE new file mode 100644 index 000000000..519a7e995 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/dockerfile/NOTICE @@ -0,0 +1,26 @@ +Source files in this directory and all sub-directories have been +copied from github.com/docker/docker/builder/dockerfile and are +Licensed under the Apache License Version 2.0. + +Note that the fork of github.com/docker/docker used commit +b68221c37ee597950364788204546f9c9d0e46a1. + +Docker +Copyright 2012-2017 Docker, Inc. + +This product includes software developed at Docker, Inc. (https://www.docker.com). + +This product contains software (https://github.com/kr/pty) developed +by Keith Rarick, licensed under the MIT License. + +The following is courtesy of our legal counsel: + + +Use and transfer of Docker may be subject to certain restrictions by the +United States and other governments. +It is your responsibility to ensure that your use and/or transfer does not +violate applicable laws. + +For more information, please see https://www.bis.doc.gov + +See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/vendor/github.com/docker/docker/builder/dockerfile/command/command.go b/vendor/github.com/openshift/imagebuilder/dockerfile/command/command.go index f23c6874b..f23c6874b 100644 --- a/vendor/github.com/docker/docker/builder/dockerfile/command/command.go +++ b/vendor/github.com/openshift/imagebuilder/dockerfile/command/command.go diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/line_parsers.go b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go index 2c375b74e..82d912b26 100644 --- a/vendor/github.com/docker/docker/builder/dockerfile/parser/line_parsers.go +++ b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/line_parsers.go @@ -15,7 +15,7 @@ import ( "unicode" "unicode/utf8" - "github.com/docker/docker/builder/dockerfile/command" + "github.com/openshift/imagebuilder/dockerfile/command" ) var ( diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/parser.go b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/parser.go index 822c42b41..0223963e1 100644 --- a/vendor/github.com/docker/docker/builder/dockerfile/parser/parser.go +++ b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/parser.go @@ -12,7 +12,7 @@ import ( "strings" "unicode" - "github.com/docker/docker/builder/dockerfile/command" + "github.com/openshift/imagebuilder/dockerfile/command" "github.com/docker/docker/pkg/system" "github.com/pkg/errors" ) @@ -91,6 +91,9 @@ var ( // DefaultEscapeToken is the default escape token const DefaultEscapeToken = '\\' +// defaultPlatformToken is the platform assumed for the build if not explicitly provided +var defaultPlatformToken = runtime.GOOS + // Directive is the structure used during a build run to hold the state of // parsing directives. type Directive struct { @@ -140,7 +143,7 @@ func (d *Directive) possibleParserDirective(line string) error { if len(tecMatch) != 0 { for i, n := range tokenEscapeCommand.SubexpNames() { if n == "escapechar" { - if d.escapeSeen { + if d.escapeSeen == true { return errors.New("only one escape parser directive can be used") } d.escapeSeen = true @@ -149,13 +152,14 @@ func (d *Directive) possibleParserDirective(line string) error { } } - // Only recognise a platform token if LCOW is supported + // TODO @jhowardmsft LCOW Support: Eventually this check can be removed, + // but only recognise a platform token if running in LCOW mode. if system.LCOWSupported() { tpcMatch := tokenPlatformCommand.FindStringSubmatch(strings.ToLower(line)) if len(tpcMatch) != 0 { for i, n := range tokenPlatformCommand.SubexpNames() { if n == "platform" { - if d.platformSeen { + if d.platformSeen == true { return errors.New("only one platform parser directive can be used") } d.platformSeen = true @@ -173,6 +177,7 @@ func (d *Directive) possibleParserDirective(line string) error { func NewDefaultDirective() *Directive { directive := Directive{} directive.setEscapeToken(string(DefaultEscapeToken)) + directive.setPlatformToken(defaultPlatformToken) return &directive } @@ -237,10 +242,8 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) { type Result struct { AST *Node EscapeToken rune - // TODO @jhowardmsft - see https://github.com/moby/moby/issues/34617 - // This next field will be removed in a future update for LCOW support. - OS string - Warnings []string + Platform string + Warnings []string } // PrintWarnings to the writer @@ -287,10 +290,6 @@ func Parse(rwc io.Reader) (*Result, error) { } currentLine++ - if isComment(scanner.Bytes()) { - // original line was a comment (processLine strips comments) - continue - } if isEmptyContinuationLine(bytesRead) { hasEmptyContinuationLine = true continue @@ -320,7 +319,7 @@ func Parse(rwc io.Reader) (*Result, error) { AST: root, Warnings: warnings, EscapeToken: d.escapeToken, - OS: d.platformToken, + Platform: d.platformToken, }, nil } @@ -332,12 +331,8 @@ func trimWhitespace(src []byte) []byte { return bytes.TrimLeftFunc(src, unicode.IsSpace) } -func isComment(line []byte) bool { - return tokenComment.Match(trimWhitespace(line)) -} - func isEmptyContinuationLine(line []byte) bool { - return len(trimWhitespace(line)) == 0 + return len(trimComments(trimWhitespace(line))) == 0 } var utf8bom = []byte{0xEF, 0xBB, 0xBF} diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/split_command.go b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/split_command.go index 171f454f6..171f454f6 100644 --- a/vendor/github.com/docker/docker/builder/dockerfile/parser/split_command.go +++ b/vendor/github.com/openshift/imagebuilder/dockerfile/parser/split_command.go diff --git a/vendor/github.com/openshift/imagebuilder/evaluator.go b/vendor/github.com/openshift/imagebuilder/evaluator.go index e1cd5d6d6..1ea358451 100644 --- a/vendor/github.com/openshift/imagebuilder/evaluator.go +++ b/vendor/github.com/openshift/imagebuilder/evaluator.go @@ -5,8 +5,8 @@ import ( "io" "strings" - "github.com/docker/docker/builder/dockerfile/command" - "github.com/docker/docker/builder/dockerfile/parser" + "github.com/openshift/imagebuilder/dockerfile/command" + "github.com/openshift/imagebuilder/dockerfile/parser" ) // ParseDockerfile parses the provided stream as a canonical Dockerfile diff --git a/vendor/github.com/openshift/imagebuilder/vendor.conf b/vendor/github.com/openshift/imagebuilder/vendor.conf new file mode 100644 index 000000000..39b216feb --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/vendor.conf @@ -0,0 +1,21 @@ +github.com/Azure/go-ansiterm d6e3b3328b783f23731bc4d058875b0371ff8109 +github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d +github.com/docker/docker b68221c37ee597950364788204546f9c9d0e46a1 +github.com/docker/go-connections 97c2040d34dfae1d1b1275fa3a78dbdd2f41cf7e +github.com/docker/go-units 2fb04c6466a548a03cb009c5569ee1ab1e35398e +github.com/fsouza/go-dockerclient openshift-4.0 https://github.com/openshift/go-dockerclient.git +github.com/gogo/protobuf c5a62797aee0054613cc578653a16c6237fef080 +github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998 +github.com/golang/protobuf v1.3.0 +github.com/konsorten/go-windows-terminal-sequences f55edac94c9bbba5d6182a4be46d86a2c9b5b50e +github.com/Microsoft/go-winio 1a8911d1ed007260465c3bfbbc785ac6915a0bb8 +github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 +github.com/opencontainers/go-digest ac19fd6e7483ff933754af248d80be865e543d22 +github.com/opencontainers/image-spec 243ea084a44451d27322fed02b682d99e2af3ba9 +github.com/opencontainers/runc 923a8f8a9a07aceada5fc48c4d37e905d9b019b5 +github.com/pkg/errors 27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7 +github.com/sirupsen/logrus d7b6bf5e4d26448fd977d07d745a2a66097ddecb +golang.org/x/crypto ff983b9c42bc9fbf91556e191cc8efb585c16908 +golang.org/x/net 45ffb0cd1ba084b73e26dee67e667e1be5acce83 +golang.org/x/sync 37e7f081c4d4c64e13b10787722085407fe5d15f +golang.org/x/sys 7fbe1cd0fcc20051e1fcb87fbabec4a1bacaaeba diff --git a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go b/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go deleted file mode 100644 index 37dc0cfdb..000000000 --- a/vendor/golang.org/x/net/context/ctxhttp/ctxhttp.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package ctxhttp provides helper functions for performing context-aware HTTP requests. -package ctxhttp // import "golang.org/x/net/context/ctxhttp" - -import ( - "context" - "io" - "net/http" - "net/url" - "strings" -) - -// Do sends an HTTP request with the provided http.Client and returns -// an HTTP response. -// -// If the client is nil, http.DefaultClient is used. -// -// The provided ctx must be non-nil. If it is canceled or times out, -// ctx.Err() will be returned. -func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { - if client == nil { - client = http.DefaultClient - } - resp, err := client.Do(req.WithContext(ctx)) - // If we got an error, and the context has been canceled, - // the context's error is probably more useful. - if err != nil { - select { - case <-ctx.Done(): - err = ctx.Err() - default: - } - } - return resp, err -} - -// Get issues a GET request via the Do function. -func Get(ctx context.Context, client *http.Client, url string) (*http.Response, error) { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - return Do(ctx, client, req) -} - -// Head issues a HEAD request via the Do function. -func Head(ctx context.Context, client *http.Client, url string) (*http.Response, error) { - req, err := http.NewRequest("HEAD", url, nil) - if err != nil { - return nil, err - } - return Do(ctx, client, req) -} - -// Post issues a POST request via the Do function. -func Post(ctx context.Context, client *http.Client, url string, bodyType string, body io.Reader) (*http.Response, error) { - req, err := http.NewRequest("POST", url, body) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", bodyType) - return Do(ctx, client, req) -} - -// PostForm issues a POST request via the Do function. -func PostForm(ctx context.Context, client *http.Client, url string, data url.Values) (*http.Response, error) { - return Post(ctx, client, url, "application/x-www-form-urlencoded", strings.NewReader(data.Encode())) -} diff --git a/vendor/golang.org/x/sys/windows/registry/key.go b/vendor/golang.org/x/sys/windows/registry/key.go new file mode 100644 index 000000000..c25648343 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/key.go @@ -0,0 +1,198 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +// Package registry provides access to the Windows registry. +// +// Here is a simple example, opening a registry key and reading a string value from it. +// +// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) +// if err != nil { +// log.Fatal(err) +// } +// defer k.Close() +// +// s, _, err := k.GetStringValue("SystemRoot") +// if err != nil { +// log.Fatal(err) +// } +// fmt.Printf("Windows system root is %q\n", s) +// +package registry + +import ( + "io" + "syscall" + "time" +) + +const ( + // Registry key security and access rights. + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx + // for details. + ALL_ACCESS = 0xf003f + CREATE_LINK = 0x00020 + CREATE_SUB_KEY = 0x00004 + ENUMERATE_SUB_KEYS = 0x00008 + EXECUTE = 0x20019 + NOTIFY = 0x00010 + QUERY_VALUE = 0x00001 + READ = 0x20019 + SET_VALUE = 0x00002 + WOW64_32KEY = 0x00200 + WOW64_64KEY = 0x00100 + WRITE = 0x20006 +) + +// Key is a handle to an open Windows registry key. +// Keys can be obtained by calling OpenKey; there are +// also some predefined root keys such as CURRENT_USER. +// Keys can be used directly in the Windows API. +type Key syscall.Handle + +const ( + // Windows defines some predefined root keys that are always open. + // An application can use these keys as entry points to the registry. + // Normally these keys are used in OpenKey to open new keys, + // but they can also be used anywhere a Key is required. + CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT) + CURRENT_USER = Key(syscall.HKEY_CURRENT_USER) + LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE) + USERS = Key(syscall.HKEY_USERS) + CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG) + PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA) +) + +// Close closes open key k. +func (k Key) Close() error { + return syscall.RegCloseKey(syscall.Handle(k)) +} + +// OpenKey opens a new key with path name relative to key k. +// It accepts any open key, including CURRENT_USER and others, +// and returns the new key and an error. +// The access parameter specifies desired access rights to the +// key to be opened. +func OpenKey(k Key, path string, access uint32) (Key, error) { + p, err := syscall.UTF16PtrFromString(path) + if err != nil { + return 0, err + } + var subkey syscall.Handle + err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey) + if err != nil { + return 0, err + } + return Key(subkey), nil +} + +// OpenRemoteKey opens a predefined registry key on another +// computer pcname. The key to be opened is specified by k, but +// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS. +// If pcname is "", OpenRemoteKey returns local computer key. +func OpenRemoteKey(pcname string, k Key) (Key, error) { + var err error + var p *uint16 + if pcname != "" { + p, err = syscall.UTF16PtrFromString(`\\` + pcname) + if err != nil { + return 0, err + } + } + var remoteKey syscall.Handle + err = regConnectRegistry(p, syscall.Handle(k), &remoteKey) + if err != nil { + return 0, err + } + return Key(remoteKey), nil +} + +// ReadSubKeyNames returns the names of subkeys of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadSubKeyNames(n int) ([]string, error) { + names := make([]string, 0) + // Registry key size limit is 255 bytes and described there: + // https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx + buf := make([]uint16, 256) //plus extra room for terminating zero byte +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} + +// CreateKey creates a key named path under open key k. +// CreateKey returns the new key and a boolean flag that reports +// whether the key already existed. +// The access parameter specifies the access rights for the key +// to be created. +func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) { + var h syscall.Handle + var d uint32 + err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path), + 0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d) + if err != nil { + return 0, false, err + } + return Key(h), d == _REG_OPENED_EXISTING_KEY, nil +} + +// DeleteKey deletes the subkey path of key k and its values. +func DeleteKey(k Key, path string) error { + return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path)) +} + +// A KeyInfo describes the statistics of a key. It is returned by Stat. +type KeyInfo struct { + SubKeyCount uint32 + MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte + ValueCount uint32 + MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte + MaxValueLen uint32 // longest data component among the key's values, in bytes + lastWriteTime syscall.Filetime +} + +// ModTime returns the key's last write time. +func (ki *KeyInfo) ModTime() time.Time { + return time.Unix(0, ki.lastWriteTime.Nanoseconds()) +} + +// Stat retrieves information about the open key k. +func (k Key) Stat() (*KeyInfo, error) { + var ki KeyInfo + err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil, + &ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount, + &ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime) + if err != nil { + return nil, err + } + return &ki, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/mksyscall.go b/vendor/golang.org/x/sys/windows/registry/mksyscall.go new file mode 100644 index 000000000..0ac95ffe7 --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/mksyscall.go @@ -0,0 +1,7 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package registry + +//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go diff --git a/vendor/golang.org/x/sys/windows/registry/syscall.go b/vendor/golang.org/x/sys/windows/registry/syscall.go new file mode 100644 index 000000000..e66643cba --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/syscall.go @@ -0,0 +1,32 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package registry + +import "syscall" + +const ( + _REG_OPTION_NON_VOLATILE = 0 + + _REG_CREATED_NEW_KEY = 1 + _REG_OPENED_EXISTING_KEY = 2 + + _ERROR_NO_MORE_ITEMS syscall.Errno = 259 +) + +func LoadRegLoadMUIString() error { + return procRegLoadMUIStringW.Find() +} + +//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW +//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW +//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW +//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW +//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW +//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW +//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW + +//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW diff --git a/vendor/golang.org/x/sys/windows/registry/value.go b/vendor/golang.org/x/sys/windows/registry/value.go new file mode 100644 index 000000000..71d4e15ba --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/value.go @@ -0,0 +1,384 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build windows + +package registry + +import ( + "errors" + "io" + "syscall" + "unicode/utf16" + "unsafe" +) + +const ( + // Registry value types. + NONE = 0 + SZ = 1 + EXPAND_SZ = 2 + BINARY = 3 + DWORD = 4 + DWORD_BIG_ENDIAN = 5 + LINK = 6 + MULTI_SZ = 7 + RESOURCE_LIST = 8 + FULL_RESOURCE_DESCRIPTOR = 9 + RESOURCE_REQUIREMENTS_LIST = 10 + QWORD = 11 +) + +var ( + // ErrShortBuffer is returned when the buffer was too short for the operation. + ErrShortBuffer = syscall.ERROR_MORE_DATA + + // ErrNotExist is returned when a registry key or value does not exist. + ErrNotExist = syscall.ERROR_FILE_NOT_FOUND + + // ErrUnexpectedType is returned by Get*Value when the value's type was unexpected. + ErrUnexpectedType = errors.New("unexpected key value type") +) + +// GetValue retrieves the type and data for the specified value associated +// with an open key k. It fills up buffer buf and returns the retrieved +// byte count n. If buf is too small to fit the stored value it returns +// ErrShortBuffer error along with the required buffer size n. +// If no buffer is provided, it returns true and actual buffer size n. +// If no buffer is provided, GetValue returns the value's type only. +// If the value does not exist, the error returned is ErrNotExist. +// +// GetValue is a low level function. If value's type is known, use the appropriate +// Get*Value function instead. +func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return 0, 0, err + } + var pbuf *byte + if len(buf) > 0 { + pbuf = (*byte)(unsafe.Pointer(&buf[0])) + } + l := uint32(len(buf)) + err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l) + if err != nil { + return int(l), valtype, err + } + return int(l), valtype, nil +} + +func (k Key) getValue(name string, buf []byte) (date []byte, valtype uint32, err error) { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return nil, 0, err + } + var t uint32 + n := uint32(len(buf)) + for { + err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n) + if err == nil { + return buf[:n], t, nil + } + if err != syscall.ERROR_MORE_DATA { + return nil, 0, err + } + if n <= uint32(len(buf)) { + return nil, 0, err + } + buf = make([]byte, n) + } +} + +// GetStringValue retrieves the string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringValue returns ErrNotExist. +// If value is not SZ or EXPAND_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return "", typ, err2 + } + switch typ { + case SZ, EXPAND_SZ: + default: + return "", typ, ErrUnexpectedType + } + if len(data) == 0 { + return "", typ, nil + } + u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:] + return syscall.UTF16ToString(u), typ, nil +} + +// GetMUIStringValue retrieves the localized string value for +// the specified value name associated with an open key k. +// If the value name doesn't exist or the localized string value +// can't be resolved, GetMUIStringValue returns ErrNotExist. +// GetMUIStringValue panics if the system doesn't support +// regLoadMUIString; use LoadRegLoadMUIString to check if +// regLoadMUIString is supported before calling this function. +func (k Key) GetMUIStringValue(name string) (string, error) { + pname, err := syscall.UTF16PtrFromString(name) + if err != nil { + return "", err + } + + buf := make([]uint16, 1024) + var buflen uint32 + var pdir *uint16 + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path + + // Try to resolve the string value using the system directory as + // a DLL search path; this assumes the string value is of the form + // @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320. + + // This approach works with tzres.dll but may have to be revised + // in the future to allow callers to provide custom search paths. + + var s string + s, err = ExpandString("%SystemRoot%\\system32\\") + if err != nil { + return "", err + } + pdir, err = syscall.UTF16PtrFromString(s) + if err != nil { + return "", err + } + + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed + if buflen <= uint32(len(buf)) { + break // Buffer not growing, assume race; break + } + buf = make([]uint16, buflen) + err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) + } + + if err != nil { + return "", err + } + + return syscall.UTF16ToString(buf), nil +} + +// ExpandString expands environment-variable strings and replaces +// them with the values defined for the current user. +// Use ExpandString to expand EXPAND_SZ strings. +func ExpandString(value string) (string, error) { + if value == "" { + return "", nil + } + p, err := syscall.UTF16PtrFromString(value) + if err != nil { + return "", err + } + r := make([]uint16, 100) + for { + n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r))) + if err != nil { + return "", err + } + if n <= uint32(len(r)) { + u := (*[1 << 29]uint16)(unsafe.Pointer(&r[0]))[:] + return syscall.UTF16ToString(u), nil + } + r = make([]uint16, n) + } +} + +// GetStringsValue retrieves the []string value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetStringsValue returns ErrNotExist. +// If value is not MULTI_SZ, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != MULTI_SZ { + return nil, typ, ErrUnexpectedType + } + if len(data) == 0 { + return nil, typ, nil + } + p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:len(data)/2] + if len(p) == 0 { + return nil, typ, nil + } + if p[len(p)-1] == 0 { + p = p[:len(p)-1] // remove terminating null + } + val = make([]string, 0, 5) + from := 0 + for i, c := range p { + if c == 0 { + val = append(val, string(utf16.Decode(p[from:i]))) + from = i + 1 + } + } + return val, typ, nil +} + +// GetIntegerValue retrieves the integer value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetIntegerValue returns ErrNotExist. +// If value is not DWORD or QWORD, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 8)) + if err2 != nil { + return 0, typ, err2 + } + switch typ { + case DWORD: + if len(data) != 4 { + return 0, typ, errors.New("DWORD value is not 4 bytes long") + } + return uint64(*(*uint32)(unsafe.Pointer(&data[0]))), DWORD, nil + case QWORD: + if len(data) != 8 { + return 0, typ, errors.New("QWORD value is not 8 bytes long") + } + return uint64(*(*uint64)(unsafe.Pointer(&data[0]))), QWORD, nil + default: + return 0, typ, ErrUnexpectedType + } +} + +// GetBinaryValue retrieves the binary value for the specified +// value name associated with an open key k. It also returns the value's type. +// If value does not exist, GetBinaryValue returns ErrNotExist. +// If value is not BINARY, it will return the correct value +// type and ErrUnexpectedType. +func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) { + data, typ, err2 := k.getValue(name, make([]byte, 64)) + if err2 != nil { + return nil, typ, err2 + } + if typ != BINARY { + return nil, typ, ErrUnexpectedType + } + return data, typ, nil +} + +func (k Key) setValue(name string, valtype uint32, data []byte) error { + p, err := syscall.UTF16PtrFromString(name) + if err != nil { + return err + } + if len(data) == 0 { + return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0) + } + return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data))) +} + +// SetDWordValue sets the data and type of a name value +// under key k to value and DWORD. +func (k Key) SetDWordValue(name string, value uint32) error { + return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:]) +} + +// SetQWordValue sets the data and type of a name value +// under key k to value and QWORD. +func (k Key) SetQWordValue(name string, value uint64) error { + return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:]) +} + +func (k Key) setStringValue(name string, valtype uint32, value string) error { + v, err := syscall.UTF16FromString(value) + if err != nil { + return err + } + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] + return k.setValue(name, valtype, buf) +} + +// SetStringValue sets the data and type of a name value +// under key k to value and SZ. The value must not contain a zero byte. +func (k Key) SetStringValue(name, value string) error { + return k.setStringValue(name, SZ, value) +} + +// SetExpandStringValue sets the data and type of a name value +// under key k to value and EXPAND_SZ. The value must not contain a zero byte. +func (k Key) SetExpandStringValue(name, value string) error { + return k.setStringValue(name, EXPAND_SZ, value) +} + +// SetStringsValue sets the data and type of a name value +// under key k to value and MULTI_SZ. The value strings +// must not contain a zero byte. +func (k Key) SetStringsValue(name string, value []string) error { + ss := "" + for _, s := range value { + for i := 0; i < len(s); i++ { + if s[i] == 0 { + return errors.New("string cannot have 0 inside") + } + } + ss += s + "\x00" + } + v := utf16.Encode([]rune(ss + "\x00")) + buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] + return k.setValue(name, MULTI_SZ, buf) +} + +// SetBinaryValue sets the data and type of a name value +// under key k to value and BINARY. +func (k Key) SetBinaryValue(name string, value []byte) error { + return k.setValue(name, BINARY, value) +} + +// DeleteValue removes a named value from the key k. +func (k Key) DeleteValue(name string) error { + return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name)) +} + +// ReadValueNames returns the value names of key k. +// The parameter n controls the number of returned names, +// analogous to the way os.File.Readdirnames works. +func (k Key) ReadValueNames(n int) ([]string, error) { + ki, err := k.Stat() + if err != nil { + return nil, err + } + names := make([]string, 0, ki.ValueCount) + buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character +loopItems: + for i := uint32(0); ; i++ { + if n > 0 { + if len(names) == n { + return names, nil + } + } + l := uint32(len(buf)) + for { + err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) + if err == nil { + break + } + if err == syscall.ERROR_MORE_DATA { + // Double buffer size and try again. + l = uint32(2 * len(buf)) + buf = make([]uint16, l) + continue + } + if err == _ERROR_NO_MORE_ITEMS { + break loopItems + } + return names, err + } + names = append(names, syscall.UTF16ToString(buf[:l])) + } + if n > len(names) { + return names, io.EOF + } + return names, nil +} diff --git a/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go new file mode 100644 index 000000000..3778075da --- /dev/null +++ b/vendor/golang.org/x/sys/windows/registry/zsyscall_windows.go @@ -0,0 +1,120 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package registry + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return nil + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW") + procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW") + procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") + procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") + procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") + procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW") + procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW") + procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") +) + +func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) { + r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize)) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) { + r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) { + r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result))) + if r0 != 0 { + regerrno = syscall.Errno(r0) + } + return +} + +func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { + r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) + n = uint32(r0) + if n == 0 { + if e1 != 0 { + err = errnoErr(e1) + } else { + err = syscall.EINVAL + } + } + return +} |