summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml153
-rw-r--r--.gitignore37
-rw-r--r--.golangci.yml1
-rw-r--r--Dockerfile143
-rw-r--r--Dockerfile.centos77
-rw-r--r--Dockerfile.fedora73
-rw-r--r--Dockerfile.ubuntu29
-rw-r--r--Makefile24
-rw-r--r--README.md2
-rw-r--r--RELEASE_NOTES.md9
-rw-r--r--cmd/podman/build.go470
-rw-r--r--cmd/podman/common/create.go58
-rw-r--r--cmd/podman/common/create_opts.go4
-rw-r--r--cmd/podman/common/createparse.go21
-rw-r--r--cmd/podman/common/default.go121
-rw-r--r--cmd/podman/common/inspect.go18
-rw-r--r--cmd/podman/common/netflags.go107
-rw-r--r--cmd/podman/common/ports.go118
-rw-r--r--cmd/podman/common/specgen.go499
-rw-r--r--cmd/podman/common/volumes.go569
-rw-r--r--cmd/podman/containers/attach.go47
-rw-r--r--cmd/podman/containers/commit.go36
-rw-r--r--cmd/podman/containers/container.go22
-rw-r--r--cmd/podman/containers/cp.go55
-rw-r--r--cmd/podman/containers/create.go125
-rw-r--r--cmd/podman/containers/diff.go9
-rw-r--r--cmd/podman/containers/exec.go39
-rw-r--r--cmd/podman/containers/exists.go8
-rw-r--r--cmd/podman/containers/export.go25
-rw-r--r--cmd/podman/containers/inspect.go54
-rw-r--r--cmd/podman/containers/kill.go36
-rw-r--r--cmd/podman/containers/list.go10
-rw-r--r--cmd/podman/containers/mount.go29
-rw-r--r--cmd/podman/containers/pause.go25
-rw-r--r--cmd/podman/containers/port.go123
-rw-r--r--cmd/podman/containers/ps.go47
-rw-r--r--cmd/podman/containers/restart.go40
-rw-r--r--cmd/podman/containers/rm.go55
-rw-r--r--cmd/podman/containers/run.go78
-rw-r--r--cmd/podman/containers/start.go41
-rw-r--r--cmd/podman/containers/stop.go45
-rw-r--r--cmd/podman/containers/top.go38
-rw-r--r--cmd/podman/containers/unmount.go30
-rw-r--r--cmd/podman/containers/unpause.go26
-rw-r--r--cmd/podman/containers/wait.go42
-rw-r--r--cmd/podman/diff.go17
-rw-r--r--cmd/podman/generate/generate.go28
-rw-r--r--cmd/podman/generate/systemd.go57
-rw-r--r--cmd/podman/healthcheck/healthcheck.go3
-rw-r--r--cmd/podman/images/diff.go12
-rw-r--r--cmd/podman/images/exists.go5
-rw-r--r--cmd/podman/images/history.go22
-rw-r--r--cmd/podman/images/image.go6
-rw-r--r--cmd/podman/images/images.go13
-rw-r--r--cmd/podman/images/inspect.go97
-rw-r--r--cmd/podman/images/list.go87
-rw-r--r--cmd/podman/images/load.go3
-rw-r--r--cmd/podman/images/prune.go3
-rw-r--r--cmd/podman/images/pull.go12
-rw-r--r--cmd/podman/images/push.go14
-rw-r--r--cmd/podman/images/rm.go35
-rw-r--r--cmd/podman/images/search.go27
-rw-r--r--cmd/podman/images/tree.go40
-rw-r--r--cmd/podman/inspect.go36
-rw-r--r--cmd/podman/inspect/inspect.go159
-rw-r--r--cmd/podman/login.go68
-rw-r--r--cmd/podman/logout.go57
-rw-r--r--cmd/podman/main.go12
-rw-r--r--cmd/podman/manifest/add.go50
-rw-r--r--cmd/podman/manifest/create.go44
-rw-r--r--cmd/podman/manifest/inspect.go39
-rw-r--r--cmd/podman/manifest/manifest.go28
-rw-r--r--cmd/podman/networks/network.go6
-rw-r--r--cmd/podman/parse/net.go4
-rw-r--r--cmd/podman/parse/net_test.go4
-rw-r--r--cmd/podman/pods/create.go45
-rw-r--r--cmd/podman/pods/exists.go4
-rw-r--r--cmd/podman/pods/inspect.go3
-rw-r--r--cmd/podman/pods/pod.go8
-rw-r--r--cmd/podman/pods/prune.go75
-rw-r--r--cmd/podman/pods/ps.go107
-rw-r--r--cmd/podman/pods/rm.go4
-rw-r--r--cmd/podman/pods/stats.go189
-rw-r--r--cmd/podman/pods/stop.go3
-rw-r--r--cmd/podman/registry/config.go16
-rw-r--r--cmd/podman/registry/json.go20
-rw-r--r--cmd/podman/registry/registry.go26
-rw-r--r--cmd/podman/registry/remote.go2
-rw-r--r--cmd/podman/report/diff.go3
-rw-r--r--cmd/podman/report/report.go6
-rw-r--r--cmd/podman/root.go48
-rw-r--r--cmd/podman/system/events.go3
-rw-r--r--cmd/podman/system/info.go6
-rw-r--r--cmd/podman/system/service.go30
-rw-r--r--cmd/podman/system/service_abi.go57
-rw-r--r--cmd/podman/system/service_unsupported.go14
-rw-r--r--cmd/podman/system/system.go6
-rw-r--r--cmd/podman/system/version.go9
-rw-r--r--cmd/podman/utils/alias.go2
-rw-r--r--cmd/podman/utils/utils.go22
-rw-r--r--cmd/podman/validate/args.go32
-rw-r--r--cmd/podman/validate/choice.go46
-rw-r--r--cmd/podman/volumes/create.go2
-rw-r--r--cmd/podman/volumes/inspect.go8
-rw-r--r--cmd/podman/volumes/list.go23
-rw-r--r--cmd/podman/volumes/prune.go3
-rw-r--r--cmd/podman/volumes/volume.go6
-rw-r--r--completions/bash/podman79
-rw-r--r--contrib/cirrus/lib.sh107
-rwxr-xr-xcontrib/cirrus/logformatter47
-rwxr-xr-xcontrib/cirrus/logformatter.t189
-rw-r--r--contrib/cirrus/packer/fedora_packaging.sh141
-rw-r--r--contrib/cirrus/packer/fedora_setup.sh120
-rw-r--r--contrib/cirrus/packer/libpod_base_images.yml12
-rw-r--r--contrib/cirrus/packer/libpod_images.yml5
-rw-r--r--contrib/cirrus/packer/ubuntu_packaging.sh168
-rw-r--r--contrib/cirrus/packer/ubuntu_setup.sh168
-rwxr-xr-xcontrib/cirrus/setup_environment.sh8
-rw-r--r--contrib/dependencies.txt34
-rw-r--r--contrib/gate/Dockerfile39
-rw-r--r--contrib/gate/README.md8
-rw-r--r--contrib/podmanimage/stable/Dockerfile16
-rw-r--r--contrib/podmanimage/stable/containers.conf11
-rw-r--r--contrib/podmanimage/stable/manual/Containerfile13
-rw-r--r--contrib/podmanimage/testing/Dockerfile16
-rw-r--r--contrib/podmanimage/upstream/Dockerfile18
-rw-r--r--contrib/spec/podman.spec.in23
-rw-r--r--docs/source/markdown/podman-image-prune.1.md2
-rw-r--r--docs/source/markdown/podman-manifest-add.1.md76
-rw-r--r--docs/source/markdown/podman-manifest-create.1.md43
-rw-r--r--docs/source/markdown/podman-manifest-inspect.1.md24
-rw-r--r--docs/source/markdown/podman-manifest.1.md23
-rw-r--r--docs/source/markdown/podman-pod-stats.1.md2
-rw-r--r--docs/source/markdown/podman-pull.1.md14
-rw-r--r--docs/source/markdown/podman-push.1.md4
-rw-r--r--docs/source/markdown/podman.1.md1
-rw-r--r--go.mod4
-rw-r--r--go.sum9
-rwxr-xr-xhack/golangci-lint.sh15
-rw-r--r--install.md2
-rw-r--r--libpod/container_internal.go6
-rw-r--r--libpod/container_internal_linux.go27
-rw-r--r--libpod/define/config.go4
-rw-r--r--libpod/define/errors.go3
-rw-r--r--libpod/define/pod_inspect.go2
-rw-r--r--libpod/healthcheck.go2
-rw-r--r--libpod/image/docker_registry_options.go3
-rw-r--r--libpod/image/image.go112
-rw-r--r--libpod/image/manifests.go6
-rw-r--r--libpod/image/prune.go22
-rw-r--r--libpod/image/pull.go4
-rw-r--r--libpod/networking_linux.go14
-rw-r--r--libpod/options.go7
-rw-r--r--libpod/pod.go21
-rw-r--r--libpod/pod_api.go7
-rw-r--r--libpod/runtime.go5
-rw-r--r--libpod/runtime_ctr.go3
-rw-r--r--libpod/runtime_img.go6
-rw-r--r--libpod/runtime_pod.go3
-rw-r--r--pkg/api/handlers/compat/containers_create.go8
-rw-r--r--pkg/api/handlers/compat/containers_prune.go35
-rw-r--r--pkg/api/handlers/compat/events.go4
-rw-r--r--pkg/api/handlers/compat/info.go2
-rw-r--r--pkg/api/handlers/libpod/containers_create.go3
-rw-r--r--pkg/api/handlers/libpod/images.go34
-rw-r--r--pkg/api/handlers/libpod/pods.go65
-rw-r--r--pkg/api/handlers/libpod/swagger.go7
-rw-r--r--pkg/api/handlers/libpod/system.go71
-rw-r--r--pkg/api/handlers/libpod/volumes.go15
-rw-r--r--pkg/api/handlers/swagger/swagger.go16
-rw-r--r--pkg/api/handlers/types.go62
-rw-r--r--pkg/api/handlers/utils/errors.go3
-rw-r--r--pkg/api/server/register_images.go32
-rw-r--r--pkg/api/server/register_pods.go35
-rw-r--r--pkg/api/server/register_system.go17
-rw-r--r--pkg/api/server/server.go44
-rw-r--r--pkg/api/types/types.go9
-rw-r--r--pkg/apparmor/apparmor.go19
-rw-r--r--pkg/apparmor/apparmor_linux.go289
-rw-r--r--pkg/apparmor/apparmor_linux_template.go49
-rw-r--r--pkg/apparmor/apparmor_linux_test.go140
-rw-r--r--pkg/apparmor/apparmor_unsupported.go31
-rw-r--r--pkg/bindings/connection.go6
-rw-r--r--pkg/bindings/images/images.go54
-rw-r--r--pkg/bindings/pods/pods.go41
-rw-r--r--pkg/bindings/system/system.go30
-rw-r--r--pkg/bindings/test/common_test.go4
-rw-r--r--pkg/bindings/test/containers_test.go4
-rw-r--r--pkg/bindings/test/create_test.go2
-rw-r--r--pkg/bindings/test/info_test.go2
-rw-r--r--pkg/bindings/test/pods_test.go39
-rw-r--r--pkg/bindings/test/system_test.go106
-rw-r--r--pkg/domain/entities/container_ps.go30
-rw-r--r--pkg/domain/entities/containers.go25
-rw-r--r--pkg/domain/entities/engine_container.go9
-rw-r--r--pkg/domain/entities/engine_image.go10
-rw-r--r--pkg/domain/entities/events.go61
-rw-r--r--pkg/domain/entities/generate.go22
-rw-r--r--pkg/domain/entities/images.go62
-rw-r--r--pkg/domain/entities/manifest.go16
-rw-r--r--pkg/domain/entities/pods.go59
-rw-r--r--pkg/domain/entities/system.go14
-rw-r--r--pkg/domain/entities/types.go45
-rw-r--r--pkg/domain/infra/abi/containers.go78
-rw-r--r--pkg/domain/infra/abi/cp.go433
-rw-r--r--pkg/domain/infra/abi/events.go2
-rw-r--r--pkg/domain/infra/abi/generate.go174
-rw-r--r--pkg/domain/infra/abi/healthcheck.go2
-rw-r--r--pkg/domain/infra/abi/images.go304
-rw-r--r--pkg/domain/infra/abi/images_list.go13
-rw-r--r--pkg/domain/infra/abi/manifest.go102
-rw-r--r--pkg/domain/infra/abi/pods.go44
-rw-r--r--pkg/domain/infra/abi/pods_stats.go85
-rw-r--r--pkg/domain/infra/abi/runtime.go6
-rw-r--r--pkg/domain/infra/abi/system.go38
-rw-r--r--pkg/domain/infra/abi/terminal/sigproxy_linux.go2
-rw-r--r--pkg/domain/infra/abi/terminal/terminal.go2
-rw-r--r--pkg/domain/infra/abi/terminal/terminal_linux.go2
-rw-r--r--pkg/domain/infra/runtime_abi.go4
-rw-r--r--pkg/domain/infra/runtime_image_proxy.go2
-rw-r--r--pkg/domain/infra/runtime_libpod.go50
-rw-r--r--pkg/domain/infra/runtime_proxy.go2
-rw-r--r--pkg/domain/infra/runtime_tunnel.go4
-rw-r--r--pkg/domain/infra/tunnel/containers.go16
-rw-r--r--pkg/domain/infra/tunnel/events.go5
-rw-r--r--pkg/domain/infra/tunnel/generate.go12
-rw-r--r--pkg/domain/infra/tunnel/images.go57
-rw-r--r--pkg/domain/infra/tunnel/manifest.go64
-rw-r--r--pkg/domain/infra/tunnel/pods.go8
-rw-r--r--pkg/domain/infra/tunnel/system.go4
-rw-r--r--pkg/namespaces/namespaces.go7
-rw-r--r--pkg/ps/ps.go1
-rw-r--r--pkg/rootless/rootless_linux.c63
-rw-r--r--pkg/rootless/rootless_linux.go113
-rw-r--r--pkg/rootlessport/rootlessport_linux.go26
-rw-r--r--pkg/selinux/selinux.go40
-rw-r--r--pkg/spec/spec.go11
-rw-r--r--pkg/spec/spec_test.go2
-rw-r--r--pkg/specgen/container_validate.go15
-rw-r--r--pkg/specgen/generate/config_linux_cgo.go3
-rw-r--r--pkg/specgen/generate/container.go113
-rw-r--r--pkg/specgen/generate/container_create.go107
-rw-r--r--pkg/specgen/generate/namespaces.go619
-rw-r--r--pkg/specgen/generate/oci.go124
-rw-r--r--pkg/specgen/generate/pod_create.go7
-rw-r--r--pkg/specgen/generate/security.go110
-rw-r--r--pkg/specgen/generate/storage.go961
-rw-r--r--pkg/specgen/namespaces.go157
-rw-r--r--pkg/specgen/pod_validate.go10
-rw-r--r--pkg/specgen/specgen.go76
-rw-r--r--pkg/sysinfo/README.md1
-rw-r--r--pkg/sysinfo/numcpu.go12
-rw-r--r--pkg/sysinfo/numcpu_linux.go44
-rw-r--r--pkg/sysinfo/numcpu_windows.go37
-rw-r--r--pkg/sysinfo/sysinfo.go153
-rw-r--r--pkg/sysinfo/sysinfo_linux.go261
-rw-r--r--pkg/sysinfo/sysinfo_linux_test.go104
-rw-r--r--pkg/sysinfo/sysinfo_solaris.go122
-rw-r--r--pkg/sysinfo/sysinfo_test.go26
-rw-r--r--pkg/sysinfo/sysinfo_unix.go9
-rw-r--r--pkg/sysinfo/sysinfo_windows.go9
-rw-r--r--pkg/systemd/activation.go29
-rw-r--r--pkg/util/utils.go149
-rw-r--r--pkg/varlinkapi/create.go2
-rw-r--r--pkg/varlinkapi/intermediate_varlink.go2
-rw-r--r--test/apiv2/10-images.at6
-rw-r--r--test/e2e/build_test.go1
-rw-r--r--test/e2e/common_test.go19
-rw-r--r--test/e2e/config.go4
-rw-r--r--test/e2e/containers_conf_test.go1
-rw-r--r--test/e2e/cp_test.go13
-rw-r--r--test/e2e/create_test.go1
-rw-r--r--test/e2e/exists_test.go37
-rw-r--r--test/e2e/generate_kube_test.go1
-rw-r--r--test/e2e/images_test.go97
-rw-r--r--test/e2e/info_test.go55
-rw-r--r--test/e2e/login_logout_test.go1
-rw-r--r--test/e2e/manifest_test.go101
-rw-r--r--test/e2e/network_create_test.go1
-rw-r--r--test/e2e/network_test.go1
-rw-r--r--test/e2e/pause_test.go33
-rw-r--r--test/e2e/play_kube_test.go1
-rw-r--r--test/e2e/pod_infra_container_test.go15
-rw-r--r--test/e2e/pod_inspect_test.go5
-rw-r--r--test/e2e/pod_pause_test.go2
-rw-r--r--test/e2e/pod_pod_namespaces.go4
-rw-r--r--test/e2e/pod_prune_test.go23
-rw-r--r--test/e2e/pod_ps_test.go17
-rw-r--r--test/e2e/port_test.go1
-rw-r--r--test/e2e/prune_test.go31
-rw-r--r--test/e2e/ps_test.go1
-rw-r--r--test/e2e/rmi_test.go88
-rw-r--r--test/e2e/run_entrypoint_test.go1
-rw-r--r--test/e2e/run_env_test.go138
-rw-r--r--test/e2e/run_networking_test.go1
-rw-r--r--test/e2e/run_test.go90
-rw-r--r--test/e2e/runlabel_test.go1
-rw-r--r--test/e2e/save_test.go12
-rw-r--r--test/e2e/start_test.go1
-rw-r--r--test/e2e/stats_test.go1
-rw-r--r--test/e2e/system_df_test.go1
-rw-r--r--test/e2e/system_reset_test.go1
-rw-r--r--test/e2e/systemd_test.go1
-rw-r--r--test/e2e/trust_test.go1
-rw-r--r--test/e2e/version_test.go27
-rw-r--r--test/e2e/volume_ls_test.go1
-rw-r--r--test/e2e/volume_prune_test.go1
-rw-r--r--test/system/150-login.bats2
-rw-r--r--test/system/250-generate-systemd.bats2
-rw-r--r--test/utils/utils.go2
-rw-r--r--vendor/github.com/containers/common/pkg/auth/auth.go182
-rw-r--r--vendor/github.com/containers/common/pkg/auth/cli.go47
-rw-r--r--vendor/github.com/containers/common/pkg/config/default.go9
-rw-r--r--vendor/github.com/containers/common/pkg/config/libpodConfig.go6
-rw-r--r--vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go39
-rw-r--r--vendor/modules.txt5
316 files changed, 9486 insertions, 5449 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index d8b0a3bf9..5898fa160 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -34,12 +34,12 @@ env:
####
#### Cache-image names to test with (double-quotes around names are critical)
###
- FEDORA_NAME: "fedora-31"
- PRIOR_FEDORA_NAME: "fedora-30"
+ FEDORA_NAME: "fedora-32"
+ PRIOR_FEDORA_NAME: "fedora-31"
UBUNTU_NAME: "ubuntu-19"
PRIOR_UBUNTU_NAME: "ubuntu-18"
- _BUILT_IMAGE_SUFFIX: "libpod-6465271544152064"
+ _BUILT_IMAGE_SUFFIX: "libpod-6220812239765504"
FEDORA_CACHE_IMAGE_NAME: "${FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}"
PRIOR_FEDORA_CACHE_IMAGE_NAME: "${PRIOR_FEDORA_NAME}-${_BUILT_IMAGE_SUFFIX}"
UBUNTU_CACHE_IMAGE_NAME: "${UBUNTU_NAME}-${_BUILT_IMAGE_SUFFIX}"
@@ -156,6 +156,32 @@ gating_task:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
+# Ensure these container images can build
+container_image_build_task:
+ alias: 'container_image_build'
+ depends_on:
+ - "gating"
+
+ # Only run for PRs, quay.io will automatically build after bramch-push
+ only_if: $CIRRUS_BRANCH != $DEST_BRANCH
+
+ matrix:
+ - name: "build in_podman image ${FEDORA_NAME} "
+ container:
+ dockerfile: Dockerfile
+ - name: "build in_podman image ${UBUNTU_NAME}"
+ container:
+ dockerfile: Dockerfile.ubuntu
+ - name: "build gate image $DEST_BRANCH branch"
+ container:
+ dockerfile: contrib/gate/Dockerfile
+
+ container:
+ dockerfile: Dockerfile
+
+ script: make install.remote
+
+
# This task checks to make sure that we can still build an rpm from the
# source code using contrib/rpm/podman.spec.in
rpmbuild_task:
@@ -382,7 +408,6 @@ image_prune_task:
# This task does the unit and integration testing for every platform
testing_task:
- skip: $CI == 'true'
alias: "testing"
depends_on:
- "gating"
@@ -390,6 +415,7 @@ testing_task:
- "varlink_api"
- "build_each_commit"
- "build_without_cgo"
+ - "container_image_build"
# Only test build cache-images, if that's what's requested
only_if: >-
@@ -400,16 +426,16 @@ testing_task:
- name: "test ${FEDORA_NAME}"
gce_instance:
image_name: "${FEDORA_CACHE_IMAGE_NAME}"
- # FIXME
- #- name: "test ${PRIOR_FEDORA_NAME}"
- # gce_instance:
- # image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}"
- # Multiple test failures on Ubuntu 19 - Fixes TBD in future PR
- # TODO: image_name: "${UBUNTU_CACHE_IMAGE_NAME}"
- # FIXME
- #- name: "test ${PRIOR_UBUNTU_NAME}"
- # gce_instance:
- # image_name: "${PRIOR_UBUNTU_CACHE_IMAGE_NAME}"
+ # TODO:
+ # - name: "test ${PRIOR_FEDORA_NAME}"
+ # gce_instance:
+ # image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}"
+ # - name: "test ${UBUNTU_NAME}"
+ # gce_instance:
+ # image_name: "${UBUNTU_CACHE_IMAGE_NAME}"
+ # - name: "test ${PRIOR_UBUNTU_NAME}"
+ # gce_instance:
+ # image_name: "${PRIOR_UBUNTU_CACHE_IMAGE_NAME}"
timeout_in: 120m
@@ -427,7 +453,8 @@ testing_task:
networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh'
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
unit_test_script: '$SCRIPT_BASE/unit_test.sh |& ${TIMESTAMP}'
- integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
+ # FIXME
+ #integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
system_test_script: '$SCRIPT_BASE/system_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} system_test'
apiv2_test_script: '$SCRIPT_BASE/apiv2_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} apiv2_test'
@@ -451,6 +478,47 @@ testing_task:
path: "*.log.html"
type: "text/html"
+# This task only temporary as we creep up on making
+# all tests passing for v2. Once all tests pass, we
+# should immediately remove this and re-enable the
+# testing matrix.
+integration_test_temporary_task:
+
+ depends_on:
+ - "gating"
+ - "varlink_api"
+ - "vendor"
+ - "build_each_commit"
+ - "build_without_cgo"
+
+ only_if: >-
+ $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:IMG.*' &&
+ $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*'
+
+ env:
+ ADD_SECOND_PARTITION: 'true'
+ TEST_REMOTE_CLIENT: 'false'
+
+ timeout_in: 60m
+
+ networking_script: '${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/networking.sh'
+ setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
+ integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP} | ${LOGFORMAT} integration_test'
+
+ on_failure:
+ failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
+
+ always:
+ package_versions_script: '$SCRIPT_BASE/logcollector.sh packages'
+ ginkgo_node_logs_script: '$SCRIPT_BASE/logcollector.sh ginkgo'
+ df_script: '$SCRIPT_BASE/logcollector.sh df'
+ audit_log_script: '$SCRIPT_BASE/logcollector.sh audit'
+ journal_script: '$SCRIPT_BASE/logcollector.sh journal'
+ varlink_script: '$SCRIPT_BASE/logcollector.sh varlink'
+ podman_system_info_script: '$SCRIPT_BASE/logcollector.sh podman'
+ html_artifacts:
+ path: "*.log.html"
+ type: "text/html"
# This task executes tests under unique environments/conditions
special_testing_rootless_task:
@@ -509,21 +577,17 @@ special_testing_in_podman_task:
$CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*'
matrix:
- # FIXME: Integration testing currently broken for F31 hosts
- # Error: container_linux.go:345: starting container process caused "process_linux.go:281: applying cgroup configuration for process caused \"mountpoint for cgroup not found\"": OCI runtime error
- # image_name: "${FEDORA_CACHE_IMAGE_NAME}"
- name: "in-podman ${PRIOR_FEDORA_NAME}"
gce_instance:
image_name: "${PRIOR_FEDORA_CACHE_IMAGE_NAME}"
+ - name: "in-podman ${FEDORA_NAME}"
+ gce_instance:
+ image_name: "${FEDORA_CACHE_IMAGE_NAME}"
env:
ADD_SECOND_PARTITION: 'true'
MOD_LIBPOD_CONF: 'false' # Use existing/native setup
SPECIALMODE: 'in_podman' # See docs
- # TODO: Support both runc and crun (cgroups v1 and v2 container images)
- # matrix:
- # IN_PODMAN_IMAGE: "quay.io/libpod/in_podman:latest"
- # IN_PODMAN_IMAGE: "quay.io/libpod/in_podman_cgv2:latest"
timeout_in: 60m
@@ -642,6 +706,7 @@ test_build_cache_images_task:
depends_on:
- "gating"
+ - 'container_image_build'
# VMs created by packer are not cleaned up by cirrus, must allow task to complete
auto_cancellation: $CI != "true"
@@ -716,47 +781,6 @@ verify_test_built_images_task:
<<: *standardlogs
-#test_building_snap_task:
-#
-# depends_on:
-# - "gating"
-#
-# only_if: >-
-# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:IMG.*' &&
-# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*'
-#
-# container:
-# image: yakshaveinc/snapcraft:core18
-# snapcraft_script:
-# - 'apt-get -y update'
-# - 'cd contrib/snapcraft && snapcraft'
-#
-#
-#upload_snap_task:
-# only_if: >-
-# $CIRRUS_BRANCH != $DEST_BRANCH &&
-# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:IMG.*' &&
-# $CIRRUS_CHANGE_MESSAGE !=~ '.*CI:DOCS.*'
-#
-# # Only when PR or branch is merged into master
-#
-# depends_on:
-# - "test_building_snap"
-#
-# container:
-# image: yakshaveinc/snapcraft:core18
-#
-# env:
-# SNAPCRAFT_LOGIN: ENCRYPTED[d8e82eb31c6372fec07f405f413d57806026b1a9f8400033531ebcd54d6750a5e4a8b1f68e3ec65c98c65e0d9b2a6a75]
-# snapcraft_login_file:
-# path: /root/.snapcraft/login.cfg
-# variable_name: SNAPCRAFT_LOGIN
-# snapcraft_script:
-# - 'apt-get -y update'
-# - 'snapcraft login --with "/root/.snapcraft/login.cfg"'
-# - 'cd contrib/snapcraft && snapcraft && snapcraft push *.snap --release edge'
-
-
docs_task:
# Don't run this when building/testing new VM images
@@ -784,6 +808,7 @@ success_task:
- "varlink_api"
- "build_each_commit"
- "build_without_cgo"
+ - "container_image_build"
- "meta"
- "image_prune"
- "testing"
@@ -796,6 +821,8 @@ success_task:
- "test_build_cache_images"
- "verify_test_built_images"
- "docs"
+ # FIXME remove when all v2 tests pass
+ - "integration_test_temporary"
env:
CIRRUS_WORKING_DIR: "/usr/src/libpod"
diff --git a/.gitignore b/.gitignore
index d5d1206b5..e60b8c03a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,32 +1,33 @@
/.artifacts/
-/_output/
+/bin/
/brew
+/build/
+/cmd/podman/varlink/iopodman.go
+/cmd/podman/varlink/ioprojectatomicpodman.go
/conmon/
+contrib/spec/podman.spec
+*.coverprofile
/docs/*.[158]
/docs/*.[158].gz
-/docs/remote
/docs/build/
+/docs/remote
+.gopathok
+.idea*
+.nfs*
*.o
*.orig
+/_output/
/pause/pause.o
-/bin/
+pkg/api/swagger.yaml
+/pkg/varlink/iopodman.go
+podman-remote*.zip
+podman*.tar.gz
+__pycache__
+release.txt
+.ropeproject
+*.rpm
/test/bin2img/bin2img
/test/checkseccomp/checkseccomp
/test/copyimg/copyimg
/test/goecho/goecho
-/build/
-.nfs*
-.ropeproject
-__pycache__
-/cmd/podman/varlink/ioprojectatomicpodman.go
-/cmd/podman/varlink/iopodman.go
-/pkg/varlink/iopodman.go
-.gopathok
-release.txt
-podman-remote*.zip
-podman*.tar.gz
-.idea*
.vscode*
-contrib/spec/podman.spec
-*.rpm
-*.coverprofile
diff --git a/.golangci.yml b/.golangci.yml
index b04683b7b..5480b02bb 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -28,6 +28,7 @@ linters:
- misspell
- prealloc
- unparam
+ - nakedret
linters-settings:
errcheck:
check-blank: false
diff --git a/Dockerfile b/Dockerfile
index f85c47937..623747295 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,117 +1,26 @@
-FROM golang:1.12
-
-RUN apt-get update && apt-get install -y \
- apparmor \
- autoconf \
- automake \
- bison \
- build-essential \
- curl \
- e2fslibs-dev \
- file \
- gawk \
- gettext \
- go-md2man \
- iptables \
- pkg-config \
- libaio-dev \
- libcap-dev \
- libfuse-dev \
- libnet-dev \
- libnl-3-dev \
- libprotobuf-dev \
- libprotobuf-c-dev \
- libseccomp2 \
- libseccomp-dev \
- libtool \
- libudev-dev \
- protobuf-c-compiler \
- protobuf-compiler \
- libglib2.0-dev \
- libapparmor-dev \
- btrfs-tools \
- libdevmapper1.02.1 \
- libdevmapper-dev \
- libgpgme11-dev \
- liblzma-dev \
- netcat \
- socat \
- lsof \
- xz-utils \
- unzip \
- python3-yaml \
- --no-install-recommends \
- && apt-get clean
-
-# Install runc
-ENV RUNC_COMMIT 029124da7af7360afa781a0234d1b083550f797c
-RUN set -x \
- && export GOPATH="$(mktemp -d)" \
- && git clone https://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
- && cd "$GOPATH/src/github.com/opencontainers/runc" \
- && git fetch origin --tags \
- && git checkout --detach -q "$RUNC_COMMIT" \
- && make static BUILDTAGS="seccomp selinux" \
- && cp runc /usr/bin/runc \
- && rm -rf "$GOPATH"
-
-# Install conmon
-ENV CONMON_COMMIT 65fe0226d85b69fc9e527e376795c9791199153d
-RUN set -x \
- && export GOPATH="$(mktemp -d)" \
- && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
- && cd "$GOPATH/src/github.com/containers/conmon.git" \
- && git fetch origin --tags \
- && git checkout --detach -q "$CONMON_COMMIT" \
- && make \
- && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \
- && rm -rf "$GOPATH"
-
-# Install CNI plugins
-ENV CNI_COMMIT 485be65581341430f9106a194a98f0f2412245fb
-RUN set -x \
- && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \
- && git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \
- && cd "$GOPATH/src/github.com/containernetworking/plugins" \
- && git checkout --detach -q "$CNI_COMMIT" \
- && ./build_linux.sh \
- && mkdir -p /usr/libexec/cni \
- && cp bin/* /usr/libexec/cni \
- && rm -rf "$GOPATH"
-
-# Install ginkgo
-RUN set -x \
- && export GOPATH=/go \
- && go get -u github.com/onsi/ginkgo/ginkgo \
- && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/
-
-# Install gomega
-RUN set -x \
- && export GOPATH=/go \
- && go get github.com/onsi/gomega/...
-
-# Install latest stable criu version
-RUN set -x \
- && cd /tmp \
- && git clone https://github.com/checkpoint-restore/criu.git \
- && cd criu \
- && make \
- && install -D -m 755 criu/criu /usr/sbin/ \
- && rm -rf /tmp/criu
-
-# Install cni config
-#RUN make install.cni
-RUN mkdir -p /etc/cni/net.d/
-COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist
-
-# Make sure we have some policy for pulling images
-RUN mkdir -p /etc/containers && curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora -o /etc/containers/registries.conf
-
-COPY test/policy.json /etc/containers/policy.json
-COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
-
-ADD . /go/src/github.com/containers/libpod
-
-RUN set -x && cd /go/src/github.com/containers/libpod
-
-WORKDIR /go/src/github.com/containers/libpod
+FROM registry.fedoraproject.org/fedora:latest
+
+# This container image is utilized by the containers CI automation system
+# for building and testing libpod inside a container environment.
+# It is assumed that the source to be tested will overwrite $GOSRC (below)
+# at runtime.
+ENV GOPATH=/var/tmp/go
+ENV GOSRC=$GOPATH/src/github.com/containers/libpod
+ENV SCRIPT_BASE=./contrib/cirrus
+ENV PACKER_BASE=$SCRIPT_BASE/packer
+
+# Only add minimal tooling necessary to complete setup.
+ADD /$SCRIPT_BASE $GOSRC/$SCRIPT_BASE
+ADD /hack/install_catatonit.sh $GOSRC/hack/
+ADD /cni/*.conflist $GOSRC/cni/
+ADD /test/*.json $GOSRC/test/
+ADD /test/*.conf $GOSRC/test/
+WORKDIR $GOSRC
+
+# Re-use repositories and package setup as in VMs under CI
+RUN bash $PACKER_BASE/fedora_packaging.sh && \
+ dnf clean all && \
+ rm -rf /var/cache/dnf
+
+# Mirror steps taken under CI
+RUN bash -c 'source $GOSRC/$SCRIPT_BASE/lib.sh && install_test_configs'
diff --git a/Dockerfile.centos b/Dockerfile.centos
deleted file mode 100644
index f5a2b891c..000000000
--- a/Dockerfile.centos
+++ /dev/null
@@ -1,77 +0,0 @@
-FROM registry.centos.org/centos/centos:7
-
-RUN yum -y install btrfs-progs-devel \
- atomic-registries \
- autoconf \
- automake \
- bzip2 \
- device-mapper-devel \
- findutils \
- file \
- git \
- glibc-static \
- glib2-devel \
- gnupg \
- golang \
- golang-github-cpuguy83-go-md2man \
- gpgme-devel \
- libassuan-devel \
- libseccomp-devel \
- libselinux-devel \
- libtool \
- containers-common \
- runc \
- make \
- lsof \
- which\
- golang-github-cpuguy83-go-md2man \
- nmap-ncat \
- xz \
- iptables && yum clean all
-
-# Install CNI plugins
-ENV CNI_COMMIT 485be65581341430f9106a194a98f0f2412245fb
-RUN set -x \
- && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \
- && git clone https://github.com/containernetworking/plugins.git "$GOPATH/src/github.com/containernetworking/plugins" \
- && cd "$GOPATH/src/github.com/containernetworking/plugins" \
- && git checkout --detach -q "$CNI_COMMIT" \
- && ./build_linux.sh \
- && mkdir -p /usr/libexec/cni \
- && cp bin/* /usr/libexec/cni \
- && rm -rf "$GOPATH"
-
-# Install ginkgo
-RUN set -x \
- && export GOPATH=/go \
- && go get -u github.com/onsi/ginkgo/ginkgo \
- && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/
-
-# Install gomega
-RUN set -x \
- && export GOPATH=/go \
- && go get github.com/onsi/gomega/...
-
-# Install conmon
-ENV CONMON_COMMIT 6f3572558b97bc60dd8f8c7f0807748e6ce2c440
-RUN set -x \
- && export GOPATH="$(mktemp -d)" \
- && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
- && cd "$GOPATH/src/github.com/containers/conmon.git" \
- && git fetch origin --tags \
- && git checkout --detach -q "$CONMON_COMMIT" \
- && make \
- && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \
- && rm -rf "$GOPATH"
-
-# Install cni config
-#RUN make install.cni
-RUN mkdir -p /etc/cni/net.d/
-COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist
-
-# Make sure we have some policy for pulling images
-RUN mkdir -p /etc/containers
-COPY test/policy.json /etc/containers/policy.json
-COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
-
-WORKDIR /go/src/github.com/containers/libpod
diff --git a/Dockerfile.fedora b/Dockerfile.fedora
deleted file mode 100644
index 45b2c3670..000000000
--- a/Dockerfile.fedora
+++ /dev/null
@@ -1,73 +0,0 @@
-FROM registry.fedoraproject.org/fedora:30
-
-RUN dnf -y install btrfs-progs-devel \
- atomic-registries \
- autoconf \
- automake \
- bzip2 \
- device-mapper-devel \
- file \
- findutils \
- git \
- glib2-devel \
- glibc-static \
- gnupg \
- golang \
- golang-github-cpuguy83-go-md2man \
- gpgme-devel \
- libassuan-devel \
- libseccomp-devel \
- libselinux-devel \
- libtool \
- containers-common \
- runc \
- make \
- lsof \
- which\
- golang-github-cpuguy83-go-md2man \
- procps-ng \
- nmap-ncat \
- xz \
- slirp4netns \
- container-selinux \
- containernetworking-plugins \
- iproute \
- iptables && dnf clean all
-
-# Install ginkgo
-RUN set -x \
- && export GOPATH=/go GOCACHE="$(mktemp -d)" \
- && go get -u github.com/onsi/ginkgo/ginkgo \
- && install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/
-
-# Install gomega
-RUN set -x \
- && export GOPATH=/go GOCACHE="$(mktemp -d)" \
- && go get github.com/onsi/gomega/...
-
-# Install conmon
-ENV CONMON_COMMIT 6f3572558b97bc60dd8f8c7f0807748e6ce2c440
-RUN set -x \
- && export GOPATH="$(mktemp -d)" GOCACHE="$(mktemp -d)" \
- && git clone https://github.com/containers/conmon.git "$GOPATH/src/github.com/containers/conmon.git" \
- && cd "$GOPATH/src/github.com/containers/conmon.git" \
- && git fetch origin --tags \
- && git checkout --detach -q "$CONMON_COMMIT" \
- && make \
- && install -D -m 755 bin/conmon /usr/libexec/podman/conmon \
- && rm -rf "$GOPATH"
-
-# Install cni config
-#RUN make install.cni
-RUN mkdir -p /etc/cni/net.d/
-COPY cni/87-podman-bridge.conflist /etc/cni/net.d/87-podman-bridge.conflist
-
-# Make sure we have some policy for pulling images
-RUN mkdir -p /etc/containers
-COPY test/policy.json /etc/containers/policy.json
-COPY test/redhat_sigstore.yaml /etc/containers/registries.d/registry.access.redhat.com.yaml
-
-# Install varlink stuff
-RUN pip3 install varlink
-
-WORKDIR /go/src/github.com/containers/libpod
diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu
new file mode 100644
index 000000000..3a8f837b9
--- /dev/null
+++ b/Dockerfile.ubuntu
@@ -0,0 +1,29 @@
+# Must resemble $UBUNTU_BASE_IMAGE in ./contrib/cirrus/lib.sh
+FROM ubuntu:latest
+
+# This container image is intended for building and testing libpod
+# from inside a container environment. It is assumed that the source
+# to be tested will overwrite $GOSRC (below) at runtime.
+ENV GOPATH=/var/tmp/go
+ENV GOSRC=$GOPATH/src/github.com/containers/libpod
+ENV SCRIPT_BASE=./contrib/cirrus
+ENV PACKER_BASE=$SCRIPT_BASE/packer
+
+RUN export DEBIAN_FRONTEND="noninteractive" && \
+ apt-get -qq update --yes && \
+ apt-get -qq upgrade --yes && \
+ apt-get -qq install curl git && \
+ apt-get -qq autoremove --yes && \
+ rm -rf /var/cache/apt
+
+# Only add minimal tooling necessary to complete setup.
+ADD / $GOSRC
+WORKDIR $GOSRC
+
+# Re-use repositories and package setup as in VMs under CI
+RUN bash $PACKER_BASE/ubuntu_packaging.sh && \
+ apt-get -qq autoremove --yes && \
+ rm -rf /var/cache/apt
+
+# Mirror steps taken under CI
+RUN bash -c 'source $GOSRC/$SCRIPT_BASE/lib.sh && install_test_configs'
diff --git a/Makefile b/Makefile
index 5b6bcc42f..bce0f33a1 100644
--- a/Makefile
+++ b/Makefile
@@ -36,7 +36,7 @@ PKG_MANAGER ?= $(shell command -v dnf yum|head -n1)
# ~/.local/bin is not in PATH on all systems
PRE_COMMIT = $(shell command -v bin/venv/bin/pre-commit ~/.local/bin/pre-commit pre-commit | head -n1)
-SOURCES = $(shell find . -name "*.go")
+SOURCES = $(shell find . -path './.*' -prune -o -name "*.go")
GO_BUILD=$(GO) build
# Go module support: set `-mod=vendor` to use the vendored sources
@@ -470,25 +470,35 @@ changelog: ## Generate changelog
.PHONY: install
install: .gopathok install.bin install.remote install.man install.cni install.systemd ## Install binaries to system locations
-.PHONY: install.remote
-install.remote: podman-remote
+.PHONY: install.remote-nobuild
+install.remote-nobuild:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR)
install ${SELINUXOPT} -m 755 bin/podman-remote $(DESTDIR)$(BINDIR)/podman-remote
- test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman-remote
+ test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman-remote bin/podman-remote
-.PHONY: install.bin
-install.bin: podman
+.PHONY: install.remote
+install.remote: podman-remote install.remote-nobuild
+
+.PHONY: install.bin-nobuild
+install.bin-nobuild:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR)
install ${SELINUXOPT} -m 755 bin/podman $(DESTDIR)$(BINDIR)/podman
test -z "${SELINUXOPT}" || chcon --verbose --reference=$(DESTDIR)$(BINDIR)/podman bin/podman
-install.man: docs
+.PHONY: install.bin
+install.bin: podman install.bin-nobuild
+
+.PHONY: install.man-nobuild
+install.man-nobuild:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man5
install ${SELINUXOPT} -m 644 $(filter %.1,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man1
install ${SELINUXOPT} -m 644 $(filter %.5,$(MANPAGES_DEST)) -t $(DESTDIR)$(MANDIR)/man5
install ${SELINUXOPT} -m 644 docs/source/markdown/links/*1 -t $(DESTDIR)$(MANDIR)/man1
+.PHONY: install.man
+install.man: docs install.man-nobuild
+
.PHONY: install.config
install.config:
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(SHAREDIR_CONTAINERS)
diff --git a/README.md b/README.md
index 8cc010a45..8d9447e51 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
Libpod provides a library for applications looking to use the Container Pod concept,
popularized by Kubernetes. Libpod also contains the Pod Manager tool `(Podman)`. Podman manages pods, containers, container images, and container volumes.
-* [Latest Version: 1.9.0](https://github.com/containers/libpod/releases/latest)
+* [Latest Version: 1.9.1](https://github.com/containers/libpod/releases/latest)
* [Continuous Integration:](contrib/cirrus/README.md) [![Build Status](https://api.cirrus-ci.com/github/containers/libpod.svg)](https://cirrus-ci.com/github/containers/libpod/master)
* [GoDoc: ![GoDoc](https://godoc.org/github.com/containers/libpod/libpod?status.svg)](https://godoc.org/github.com/containers/libpod/libpod)
* Automated continuous release downloads (including remote-client):
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index aef66545f..6657529b9 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,14 @@
# Release Notes
+## 1.9.1
+### Bugfixes
+- Fixed a bug where healthchecks could become nonfunctional if container log paths were manually set with `--log-path` and multiple container logs were placed in the same directory ([#5915](https://github.com/containers/libpod/issues/5915))
+- Fixed a bug where rootless Podman could, when using an older `libpod.conf`, print numerous warning messages about an invalid CGroup manager config
+- Fixed a bug where rootless Podman would sometimes fail to close the rootless user namespace when joining it ([#5873](https://github.com/containers/libpod/issues/5873))
+
+### Misc
+- Updated containers/common to v0.8.2
+
## 1.9.0
### Features
- Experimental support has been added for `podman run --userns=auto`, which automatically allocates a unique UID and GID range for the new container's user namespace
diff --git a/cmd/podman/build.go b/cmd/podman/build.go
new file mode 100644
index 000000000..43a2f7ab5
--- /dev/null
+++ b/cmd/podman/build.go
@@ -0,0 +1,470 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah"
+ "github.com/containers/buildah/imagebuildah"
+ buildahCLI "github.com/containers/buildah/pkg/cli"
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/docker/go-units"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+// buildFlagsWrapper are local to cmd/ as the build code is using Buildah-internal
+// types. Hence, after parsing, we are converting buildFlagsWrapper to the entities'
+// options which essentially embed the Buildah types.
+type buildFlagsWrapper struct {
+ // Buildah stuff first
+ buildahCLI.BudResults
+ buildahCLI.LayerResults
+ buildahCLI.FromAndBudResults
+ buildahCLI.NameSpaceResults
+ buildahCLI.UserNSResults
+
+ // SquashAll squashes all layers into a single layer.
+ SquashAll bool
+}
+
+var (
+ // Command: podman _diff_ Object_ID
+ buildDescription = "Builds an OCI or Docker image using instructions from one or more Containerfiles and a specified build context directory."
+ buildCmd = &cobra.Command{
+ Use: "build [flags] [CONTEXT]",
+ Short: "Build an image using instructions from Containerfiles",
+ Long: buildDescription,
+ TraverseChildren: true,
+ RunE: build,
+ Example: `podman build .
+ podman build --creds=username:password -t imageName -f Containerfile.simple .
+ podman build --layers --force-rm --tag imageName .`,
+ }
+
+ buildOpts = buildFlagsWrapper{}
+)
+
+// useLayers returns false if BUILDAH_LAYERS is set to "0" or "false"
+// otherwise it returns true
+func useLayers() string {
+ layers := os.Getenv("BUILDAH_LAYERS")
+ if strings.ToLower(layers) == "false" || layers == "0" {
+ return "false"
+ }
+ return "true"
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: buildCmd,
+ })
+ flags := buildCmd.Flags()
+
+ // Podman flags
+ flags.BoolVarP(&buildOpts.SquashAll, "squash-all", "", false, "Squash all layers into a single layer")
+
+ // Bud flags
+ budFlags := buildahCLI.GetBudFlags(&buildOpts.BudResults)
+ // --pull flag
+ flag := budFlags.Lookup("pull")
+ if err := flag.Value.Set("true"); err != nil {
+ logrus.Errorf("unable to set --pull to true: %v", err)
+ }
+ flag.DefValue = "true"
+ flags.AddFlagSet(&budFlags)
+
+ // Layer flags
+ layerFlags := buildahCLI.GetLayerFlags(&buildOpts.LayerResults)
+ // --layers flag
+ flag = layerFlags.Lookup("layers")
+ useLayersVal := useLayers()
+ if err := flag.Value.Set(useLayersVal); err != nil {
+ logrus.Errorf("unable to set --layers to %v: %v", useLayersVal, err)
+ }
+ flag.DefValue = useLayersVal
+ // --force-rm flag
+ flag = layerFlags.Lookup("force-rm")
+ if err := flag.Value.Set("true"); err != nil {
+ logrus.Errorf("unable to set --force-rm to true: %v", err)
+ }
+ flag.DefValue = "true"
+ flags.AddFlagSet(&layerFlags)
+
+ // FromAndBud flags
+ fromAndBudFlags, err := buildahCLI.GetFromAndBudFlags(&buildOpts.FromAndBudResults, &buildOpts.UserNSResults, &buildOpts.NameSpaceResults)
+ if err != nil {
+ logrus.Errorf("error setting up build flags: %v", err)
+ os.Exit(1)
+ }
+ flags.AddFlagSet(&fromAndBudFlags)
+}
+
+// build executes the build command.
+func build(cmd *cobra.Command, args []string) error {
+ if (cmd.Flags().Changed("squash") && cmd.Flags().Changed("layers")) ||
+ (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("layers")) ||
+ (cmd.Flags().Changed("squash-all") && cmd.Flags().Changed("squash")) {
+ return errors.New("cannot specify --squash, --squash-all and --layers options together")
+ }
+
+ contextDir, containerFiles, err := extractContextAndFiles(args, buildOpts.File)
+ if err != nil {
+ return err
+ }
+
+ ie, err := registry.NewImageEngine(cmd, args)
+ if err != nil {
+ return err
+ }
+
+ apiBuildOpts, err := buildFlagsWrapperToOptions(cmd, contextDir, &buildOpts)
+ if err != nil {
+ return err
+ }
+
+ _, err = ie.Build(registry.GetContext(), containerFiles, *apiBuildOpts)
+ return err
+}
+
+// extractContextAndFiles parses args and files to extract a context directory
+// and {Container,Docker}files.
+//
+// TODO: this was copied and altered from the v1 client which in turn was
+// copied and altered from the Buildah code. Ideally, all of this code should
+// be cleanly consolidated into a package that is shared between Buildah and
+// Podman.
+func extractContextAndFiles(args, files []string) (string, []string, error) {
+ // Extract container files from the CLI (i.e., --file/-f) first.
+ var containerFiles []string
+ for _, f := range files {
+ if f == "-" {
+ containerFiles = append(containerFiles, "/dev/stdin")
+ } else {
+ containerFiles = append(containerFiles, f)
+ }
+ }
+
+ // Determine context directory.
+ var contextDir string
+ if len(args) > 0 {
+ // The context directory could be a URL. Try to handle that.
+ tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", args[0])
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error prepping temporary context directory")
+ }
+ if tempDir != "" {
+ // We had to download it to a temporary directory.
+ // Delete it later.
+ defer func() {
+ if err = os.RemoveAll(tempDir); err != nil {
+ logrus.Errorf("error removing temporary directory %q: %v", contextDir, err)
+ }
+ }()
+ contextDir = filepath.Join(tempDir, subDir)
+ } else {
+ // Nope, it was local. Use it as is.
+ absDir, err := filepath.Abs(args[0])
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error determining path to directory %q", args[0])
+ }
+ contextDir = absDir
+ }
+ } else {
+ // No context directory or URL was specified. Try to use the home of
+ // the first locally-available Containerfile.
+ for i := range containerFiles {
+ if strings.HasPrefix(containerFiles[i], "http://") ||
+ strings.HasPrefix(containerFiles[i], "https://") ||
+ strings.HasPrefix(containerFiles[i], "git://") ||
+ strings.HasPrefix(containerFiles[i], "github.com/") {
+ continue
+ }
+ absFile, err := filepath.Abs(containerFiles[i])
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error determining path to file %q", containerFiles[i])
+ }
+ contextDir = filepath.Dir(absFile)
+ break
+ }
+ }
+
+ if contextDir == "" {
+ return "", nil, errors.Errorf("no context directory and no Containerfile specified")
+ }
+ if !utils.IsDir(contextDir) {
+ return "", nil, errors.Errorf("context must be a directory: %q", contextDir)
+ }
+ if len(containerFiles) == 0 {
+ if utils.FileExists(filepath.Join(contextDir, "Containerfile")) {
+ containerFiles = append(containerFiles, filepath.Join(contextDir, "Containerfile"))
+ } else {
+ containerFiles = append(containerFiles, filepath.Join(contextDir, "Dockerfile"))
+ }
+ }
+
+ return contextDir, containerFiles, nil
+}
+
+// buildFlagsWrapperToOptions converts the local build flags to the build options used
+// in the API which embed Buildah types used across the build code. Doing the
+// conversion here prevents the API from doing that (redundantly).
+//
+// TODO: this code should really be in Buildah.
+func buildFlagsWrapperToOptions(c *cobra.Command, contextDir string, flags *buildFlagsWrapper) (*entities.BuildOptions, error) {
+ output := ""
+ tags := []string{}
+ if c.Flag("tag").Changed {
+ tags = flags.Tag
+ if len(tags) > 0 {
+ output = tags[0]
+ tags = tags[1:]
+ }
+ }
+
+ pullPolicy := imagebuildah.PullNever
+ if flags.Pull {
+ pullPolicy = imagebuildah.PullIfMissing
+ }
+ if flags.PullAlways {
+ pullPolicy = imagebuildah.PullAlways
+ }
+
+ args := make(map[string]string)
+ if c.Flag("build-arg").Changed {
+ for _, arg := range flags.BuildArg {
+ av := strings.SplitN(arg, "=", 2)
+ if len(av) > 1 {
+ args[av[0]] = av[1]
+ } else {
+ delete(args, av[0])
+ }
+ }
+ }
+ // Check to see if the BUILDAH_LAYERS environment variable is set and
+ // override command-line.
+ if _, ok := os.LookupEnv("BUILDAH_LAYERS"); ok {
+ flags.Layers = true
+ }
+
+ // `buildah bud --layers=false` acts like `docker build --squash` does.
+ // That is all of the new layers created during the build process are
+ // condensed into one, any layers present prior to this build are
+ // retained without condensing. `buildah bud --squash` squashes both
+ // new and old layers down into one. Translate Podman commands into
+ // Buildah. Squash invoked, retain old layers, squash new layers into
+ // one.
+ if c.Flags().Changed("squash") && buildOpts.Squash {
+ flags.Squash = false
+ flags.Layers = false
+ }
+ // Squash-all invoked, squash both new and old layers into one.
+ if c.Flags().Changed("squash-all") {
+ flags.Squash = true
+ flags.Layers = false
+ }
+
+ var stdin, stdout, stderr, reporter *os.File
+ stdin = os.Stdin
+ stdout = os.Stdout
+ stderr = os.Stderr
+ reporter = os.Stderr
+
+ if c.Flag("logfile").Changed {
+ f, err := os.OpenFile(flags.Logfile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
+ if err != nil {
+ return nil, errors.Errorf("error opening logfile %q: %v", flags.Logfile, err)
+ }
+ defer f.Close()
+ logrus.SetOutput(f)
+ stdout = f
+ stderr = f
+ reporter = f
+ }
+
+ var memoryLimit, memorySwap int64
+ var err error
+ if c.Flags().Changed("memory") {
+ memoryLimit, err = units.RAMInBytes(flags.Memory)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if c.Flags().Changed("memory-swap") {
+ memorySwap, err = units.RAMInBytes(flags.MemorySwap)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ nsValues, err := getNsValues(flags)
+ if err != nil {
+ return nil, err
+ }
+
+ networkPolicy := buildah.NetworkDefault
+ for _, ns := range nsValues {
+ if ns.Name == "none" {
+ networkPolicy = buildah.NetworkDisabled
+ break
+ } else if !filepath.IsAbs(ns.Path) {
+ networkPolicy = buildah.NetworkEnabled
+ break
+ }
+ }
+
+ // `buildah bud --layers=false` acts like `docker build --squash` does.
+ // That is all of the new layers created during the build process are
+ // condensed into one, any layers present prior to this build are retained
+ // without condensing. `buildah bud --squash` squashes both new and old
+ // layers down into one. Translate Podman commands into Buildah.
+ // Squash invoked, retain old layers, squash new layers into one.
+ if c.Flags().Changed("squash") && flags.Squash {
+ flags.Squash = false
+ flags.Layers = false
+ }
+ // Squash-all invoked, squash both new and old layers into one.
+ if c.Flags().Changed("squash-all") {
+ flags.Squash = true
+ flags.Layers = false
+ }
+
+ compression := imagebuildah.Gzip
+ if flags.DisableCompression {
+ compression = imagebuildah.Uncompressed
+ }
+
+ isolation, err := parse.IsolationOption(flags.Isolation)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing ID mapping options")
+ }
+
+ usernsOption, idmappingOptions, err := parse.IDMappingOptions(c, isolation)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error parsing ID mapping options")
+ }
+ nsValues = append(nsValues, usernsOption...)
+
+ systemContext, err := parse.SystemContextFromOptions(c)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error building system context")
+ }
+
+ format := ""
+ flags.Format = strings.ToLower(flags.Format)
+ switch {
+ case strings.HasPrefix(flags.Format, buildah.OCI):
+ format = buildah.OCIv1ImageManifest
+ case strings.HasPrefix(flags.Format, buildah.DOCKER):
+ format = buildah.Dockerv2ImageManifest
+ default:
+ return nil, errors.Errorf("unrecognized image type %q", flags.Format)
+ }
+
+ runtimeFlags := []string{}
+ for _, arg := range flags.RuntimeFlags {
+ runtimeFlags = append(runtimeFlags, "--"+arg)
+ }
+
+ // FIXME: the code below needs to be enabled (and adjusted) once the
+ // global/root flags are supported.
+
+ // conf, err := runtime.GetConfig()
+ // if err != nil {
+ // return err
+ // }
+ // if conf != nil && conf.Engine.CgroupManager == config.SystemdCgroupsManager {
+ // runtimeFlags = append(runtimeFlags, "--systemd-cgroup")
+ // }
+
+ opts := imagebuildah.BuildOptions{
+ AddCapabilities: flags.CapAdd,
+ AdditionalTags: tags,
+ Annotations: flags.Annotation,
+ Architecture: flags.Arch,
+ Args: args,
+ BlobDirectory: flags.BlobCache,
+ CNIConfigDir: flags.CNIConfigDir,
+ CNIPluginPath: flags.CNIPlugInPath,
+ CommonBuildOpts: &buildah.CommonBuildOptions{
+ AddHost: flags.AddHost,
+ CgroupParent: flags.CgroupParent,
+ CPUPeriod: flags.CPUPeriod,
+ CPUQuota: flags.CPUQuota,
+ CPUShares: flags.CPUShares,
+ CPUSetCPUs: flags.CPUSetCPUs,
+ CPUSetMems: flags.CPUSetMems,
+ Memory: memoryLimit,
+ MemorySwap: memorySwap,
+ ShmSize: flags.ShmSize,
+ Ulimit: flags.Ulimit,
+ Volumes: flags.Volumes,
+ },
+ Compression: compression,
+ ConfigureNetwork: networkPolicy,
+ ContextDirectory: contextDir,
+ // DefaultMountsFilePath: FIXME: this requires global flags to be working!
+ Devices: flags.Devices,
+ DropCapabilities: flags.CapDrop,
+ Err: stderr,
+ ForceRmIntermediateCtrs: flags.ForceRm,
+ IDMappingOptions: idmappingOptions,
+ IIDFile: flags.Iidfile,
+ In: stdin,
+ Isolation: isolation,
+ Labels: flags.Label,
+ Layers: flags.Layers,
+ NamespaceOptions: nsValues,
+ NoCache: flags.NoCache,
+ OS: flags.OS,
+ Out: stdout,
+ Output: output,
+ OutputFormat: format,
+ PullPolicy: pullPolicy,
+ Quiet: flags.Quiet,
+ RemoveIntermediateCtrs: flags.Rm,
+ ReportWriter: reporter,
+ RuntimeArgs: runtimeFlags,
+ SignBy: flags.SignBy,
+ SignaturePolicyPath: flags.SignaturePolicy,
+ Squash: flags.Squash,
+ SystemContext: systemContext,
+ Target: flags.Target,
+ TransientMounts: flags.Volumes,
+ }
+
+ return &entities.BuildOptions{BuildOptions: opts}, nil
+}
+
+func getNsValues(flags *buildFlagsWrapper) ([]buildah.NamespaceOption, error) {
+ var ret []buildah.NamespaceOption
+ if flags.Network != "" {
+ switch {
+ case flags.Network == "host":
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ Host: true,
+ })
+ case flags.Network == "container":
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ })
+ case flags.Network[0] == '/':
+ ret = append(ret, buildah.NamespaceOption{
+ Name: string(specs.NetworkNamespace),
+ Path: flags.Network,
+ })
+ default:
+ return nil, errors.Errorf("unsupported configuration network=%s", flags.Network)
+ }
+ }
+ return ret, nil
+}
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go
index a7c8435c9..a0aed984c 100644
--- a/cmd/podman/common/create.go
+++ b/cmd/podman/common/create.go
@@ -10,7 +10,7 @@ import (
const sizeWithUnitFormat = "(format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))"
-var containerConfig = registry.NewPodmanConfig()
+var containerConfig = registry.PodmanConfig()
func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
createFlags := pflag.FlagSet{}
@@ -49,14 +49,15 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"cap-drop", []string{},
"Drop capabilities from the container",
)
+ cgroupNS := ""
createFlags.StringVar(
- &cf.CGroupsNS,
- "cgroupns", getDefaultCgroupNS(),
+ &cgroupNS,
+ "cgroupns", containerConfig.CgroupNS(),
"cgroup namespace to use",
)
createFlags.StringVar(
- &cf.CGroups,
- "cgroups", "enabled",
+ &cf.CGroupsMode,
+ "cgroups", containerConfig.Cgroups(),
`control container cgroup configuration ("enabled"|"disabled"|"no-conmon")`,
)
createFlags.StringVar(
@@ -121,12 +122,12 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringVar(
&cf.DetachKeys,
- "detach-keys", GetDefaultDetachKeys(),
+ "detach-keys", containerConfig.DetachKeys(),
"Override the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-cf`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`",
)
createFlags.StringSliceVar(
- &cf.Device,
- "device", getDefaultDevices(),
+ &cf.Devices,
+ "device", containerConfig.Devices(),
fmt.Sprintf("Add a host device to the container"),
)
createFlags.StringSliceVar(
@@ -161,7 +162,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVarP(
&cf.env,
- "env", "e", getDefaultEnv(),
+ "env", "e", containerConfig.Env(),
"Set environment variables in container",
)
createFlags.BoolVar(
@@ -238,7 +239,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringVar(
&cf.InitPath,
- "init-path", getDefaultInitPath(),
+ "init-path", containerConfig.InitPath(),
// Do not use the Value field for setting the default value to determine user input (i.e., non-empty string)
fmt.Sprintf("Path to the container-init binary"),
)
@@ -247,9 +248,10 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"interactive", "i", false,
"Keep STDIN open even if not attached",
)
+ ipcNS := ""
createFlags.StringVar(
- &cf.IPC,
- "ipc", getDefaultIPCNS(),
+ &ipcNS,
+ "ipc", containerConfig.IPCNS(),
"IPC namespace to use",
)
createFlags.StringVar(
@@ -329,15 +331,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"use `OS` instead of the running OS for choosing images",
)
// markFlagHidden(createFlags, "override-os")
+ pid := ""
createFlags.StringVar(
- &cf.PID,
- "pid", getDefaultPidNS(),
+ &pid,
+ "pid", containerConfig.PidNS(),
"PID namespace to use",
)
createFlags.Int64Var(
&cf.PIDsLimit,
- "pids-limit", getDefaultPidsLimit(),
- getDefaultPidsDescription(),
+ "pids-limit", containerConfig.PidsLimit(),
+ "Tune container pids limit (set 0 for unlimited, -1 for server defaults)",
)
createFlags.StringVar(
&cf.Pod,
@@ -391,12 +394,13 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVar(
&cf.SecurityOpt,
- "security-opt", getDefaultSecurityOptions(),
+ "security-opt", containerConfig.SecurityOptions(),
"Security Options",
)
+ shmSize := ""
createFlags.StringVar(
- &cf.ShmSize,
- "shm-size", getDefaultShmSize(),
+ &shmSize,
+ "shm-size", containerConfig.ShmSize(),
"Size of /dev/shm "+sizeWithUnitFormat,
)
createFlags.StringVar(
@@ -427,7 +431,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
createFlags.StringSliceVar(
&cf.Sysctl,
- "sysctl", getDefaultSysctls(),
+ "sysctl", containerConfig.Sysctls(),
"Sysctl options",
)
createFlags.StringVar(
@@ -452,7 +456,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringSliceVar(
&cf.Ulimit,
- "ulimit", getDefaultUlimits(),
+ "ulimit", containerConfig.Ulimits(),
"Ulimit options",
)
createFlags.StringVarP(
@@ -460,14 +464,16 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
"user", "u", "",
"Username or UID (format: <name|uid>[:<group|gid>])",
)
+ userNS := ""
createFlags.StringVar(
- &cf.UserNS,
- "userns", getDefaultUserNS(),
+ &userNS,
+ "userns", containerConfig.Containers.UserNS,
"User namespace to use",
)
+ utsNS := ""
createFlags.StringVar(
- &cf.UTS,
- "uts", getDefaultUTSNS(),
+ &utsNS,
+ "uts", containerConfig.Containers.UTSNS,
"UTS namespace to use",
)
createFlags.StringArrayVar(
@@ -477,7 +483,7 @@ func GetCreateFlags(cf *ContainerCLIOpts) *pflag.FlagSet {
)
createFlags.StringArrayVarP(
&cf.Volume,
- "volume", "v", getDefaultVolumes(),
+ "volume", "v", containerConfig.Volumes(),
"Bind mount a volume into the container",
)
createFlags.StringSliceVar(
diff --git a/cmd/podman/common/create_opts.go b/cmd/podman/common/create_opts.go
index 9d12e4b26..2f08bb6a6 100644
--- a/cmd/podman/common/create_opts.go
+++ b/cmd/podman/common/create_opts.go
@@ -11,7 +11,7 @@ type ContainerCLIOpts struct {
CapAdd []string
CapDrop []string
CGroupsNS string
- CGroups string
+ CGroupsMode string
CGroupParent string
CIDFile string
ConmonPIDFile string
@@ -25,7 +25,7 @@ type ContainerCLIOpts struct {
CPUSetMems string
Detach bool
DetachKeys string
- Device []string
+ Devices []string
DeviceCGroupRule []string
DeviceReadBPs []string
DeviceReadIOPs []string
diff --git a/cmd/podman/common/createparse.go b/cmd/podman/common/createparse.go
index aca6f752e..fe6e322c2 100644
--- a/cmd/podman/common/createparse.go
+++ b/cmd/podman/common/createparse.go
@@ -1,7 +1,6 @@
package common
import (
- "github.com/containers/libpod/cmd/podman/parse"
"github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
@@ -17,27 +16,7 @@ func (c *ContainerCLIOpts) validate() error {
if _, err := util.ValidatePullType(c.Pull); err != nil {
return err
}
- // Verify the additional hosts are in correct format
- for _, host := range c.Net.AddHosts {
- if _, err := parse.ValidateExtraHost(host); err != nil {
- return err
- }
- }
- if dnsSearches := c.Net.DNSSearch; len(dnsSearches) > 0 {
- // Validate domains are good
- for _, dom := range dnsSearches {
- if dom == "." {
- if len(dnsSearches) > 1 {
- return errors.Errorf("cannot pass additional search domains when also specifying '.'")
- }
- continue
- }
- if _, err := parse.ValidateDomain(dom); err != nil {
- return err
- }
- }
- }
var imageVolType = map[string]string{
"bind": "",
"tmpfs": "",
diff --git a/cmd/podman/common/default.go b/cmd/podman/common/default.go
index 853f87ab6..7233b2091 100644
--- a/cmd/podman/common/default.go
+++ b/cmd/podman/common/default.go
@@ -1,16 +1,7 @@
package common
import (
- "fmt"
- "os"
-
- "github.com/containers/buildah/pkg/parse"
- "github.com/containers/libpod/pkg/apparmor"
- "github.com/containers/libpod/pkg/cgroups"
- "github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/specgen"
- "github.com/containers/libpod/pkg/sysinfo"
- "github.com/opencontainers/selinux/go-selinux"
+ "github.com/containers/libpod/cmd/podman/registry"
)
var (
@@ -24,112 +15,6 @@ var (
DefaultHealthCheckTimeout = "30s"
// DefaultImageVolume default value
DefaultImageVolume = "bind"
+ // Pull in configured json library
+ json = registry.JsonLibrary()
)
-
-// TODO these options are directly embedded into many of the CLI cobra values, as such
-// this approach will not work in a remote client. so we will need to likely do something like a
-// supported and unsupported approach here and backload these options into the specgen
-// once we are "on" the host system.
-func getDefaultSecurityOptions() []string {
- securityOpts := []string{}
- if containerConfig.Containers.SeccompProfile != "" && containerConfig.Containers.SeccompProfile != parse.SeccompDefaultPath {
- securityOpts = append(securityOpts, fmt.Sprintf("seccomp=%s", containerConfig.Containers.SeccompProfile))
- }
- if apparmor.IsEnabled() && containerConfig.Containers.ApparmorProfile != "" {
- securityOpts = append(securityOpts, fmt.Sprintf("apparmor=%s", containerConfig.Containers.ApparmorProfile))
- }
- if selinux.GetEnabled() && !containerConfig.Containers.EnableLabeling {
- securityOpts = append(securityOpts, fmt.Sprintf("label=%s", selinux.DisableSecOpt()[0]))
- }
- return securityOpts
-}
-
-// getDefaultSysctls
-func getDefaultSysctls() []string {
- return containerConfig.Containers.DefaultSysctls
-}
-
-func getDefaultVolumes() []string {
- return containerConfig.Containers.Volumes
-}
-
-func getDefaultDevices() []string {
- return containerConfig.Containers.Devices
-}
-
-func getDefaultDNSServers() []string { //nolint
- return containerConfig.Containers.DNSServers
-}
-
-func getDefaultDNSSearches() []string { //nolint
- return containerConfig.Containers.DNSSearches
-}
-
-func getDefaultDNSOptions() []string { //nolint
- return containerConfig.Containers.DNSOptions
-}
-
-func getDefaultEnv() []string {
- return containerConfig.Containers.Env
-}
-
-func getDefaultInitPath() string {
- return containerConfig.Containers.InitPath
-}
-
-func getDefaultIPCNS() string {
- return containerConfig.Containers.IPCNS
-}
-
-func getDefaultPidNS() string {
- return containerConfig.Containers.PidNS
-}
-
-func getDefaultNetNS() string { //nolint
- if containerConfig.Containers.NetNS == string(specgen.Private) && rootless.IsRootless() {
- return string(specgen.Slirp)
- }
- return containerConfig.Containers.NetNS
-}
-
-func getDefaultCgroupNS() string {
- return containerConfig.Containers.CgroupNS
-}
-
-func getDefaultUTSNS() string {
- return containerConfig.Containers.UTSNS
-}
-
-func getDefaultShmSize() string {
- return containerConfig.Containers.ShmSize
-}
-
-func getDefaultUlimits() []string {
- return containerConfig.Containers.DefaultUlimits
-}
-
-func getDefaultUserNS() string {
- userns := os.Getenv("PODMAN_USERNS")
- if userns != "" {
- return userns
- }
- return containerConfig.Containers.UserNS
-}
-
-func getDefaultPidsLimit() int64 {
- if rootless.IsRootless() {
- cgroup2, _ := cgroups.IsCgroup2UnifiedMode()
- if cgroup2 {
- return containerConfig.Containers.PidsLimit
- }
- }
- return sysinfo.GetDefaultPidsLimit()
-}
-
-func getDefaultPidsDescription() string {
- return "Tune container pids limit (set 0 for unlimited)"
-}
-
-func GetDefaultDetachKeys() string {
- return containerConfig.Engine.DetachKeys
-}
diff --git a/cmd/podman/common/inspect.go b/cmd/podman/common/inspect.go
deleted file mode 100644
index dfc6fe679..000000000
--- a/cmd/podman/common/inspect.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package common
-
-import (
- "github.com/containers/libpod/pkg/domain/entities"
- "github.com/spf13/cobra"
-)
-
-// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object
-// Since this cannot live in `package main` it lives here until a better home is found
-func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
- opts := entities.InspectOptions{}
-
- flags := cmd.Flags()
- flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size")
- flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template")
-
- return &opts
-}
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index 41eed2988..2bb45476b 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -3,7 +3,11 @@ package common
import (
"net"
+ "github.com/containers/libpod/cmd/podman/parse"
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -15,15 +19,15 @@ func GetNetFlags() *pflag.FlagSet {
"Add a custom host-to-IP mapping (host:ip) (default [])",
)
netFlags.StringSlice(
- "dns", getDefaultDNSServers(),
+ "dns", containerConfig.DNSServers(),
"Set custom DNS servers",
)
netFlags.StringSlice(
- "dns-opt", getDefaultDNSOptions(),
+ "dns-opt", containerConfig.DNSOptions(),
"Set custom DNS options",
)
netFlags.StringSlice(
- "dns-search", getDefaultDNSSearches(),
+ "dns-search", containerConfig.DNSSearches(),
"Set custom DNS search domains",
)
netFlags.String(
@@ -35,7 +39,7 @@ func GetNetFlags() *pflag.FlagSet {
"Container MAC address (e.g. 92:d0:c6:0a:29:33)",
)
netFlags.String(
- "network", getDefaultNetNS(),
+ "network", containerConfig.NetNS(),
"Connect a container to a network",
)
netFlags.StringSliceP(
@@ -58,20 +62,60 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
if err != nil {
return nil, err
}
- servers, err := cmd.Flags().GetStringSlice("dns")
- if err != nil {
- return nil, err
+ // Verify the additional hosts are in correct format
+ for _, host := range opts.AddHosts {
+ if _, err := parse.ValidateExtraHost(host); err != nil {
+ return nil, err
+ }
}
- for _, d := range servers {
- if d == "none" {
- opts.DNSHost = true
- break
+
+ if cmd.Flags().Changed("dns") {
+ servers, err := cmd.Flags().GetStringSlice("dns")
+ if err != nil {
+ return nil, err
+ }
+ for _, d := range servers {
+ if d == "none" {
+ opts.UseImageResolvConf = true
+ if len(servers) > 1 {
+ return nil, errors.Errorf("%s is not allowed to be specified with other DNS ip addresses", d)
+ }
+ break
+ }
+ dns := net.ParseIP(d)
+ if dns == nil {
+ return nil, errors.Errorf("%s is not an ip address", d)
+ }
+ opts.DNSServers = append(opts.DNSServers, dns)
}
- opts.DNSServers = append(opts.DNSServers, net.ParseIP(d))
}
- opts.DNSSearch, err = cmd.Flags().GetStringSlice("dns-search")
- if err != nil {
- return nil, err
+
+ if cmd.Flags().Changed("dns-opt") {
+ options, err := cmd.Flags().GetStringSlice("dns-opt")
+ if err != nil {
+ return nil, err
+ }
+ opts.DNSOptions = options
+ }
+
+ if cmd.Flags().Changed("dns-search") {
+ dnsSearches, err := cmd.Flags().GetStringSlice("dns-search")
+ if err != nil {
+ return nil, err
+ }
+ // Validate domains are good
+ for _, dom := range dnsSearches {
+ if dom == "." {
+ if len(dnsSearches) > 1 {
+ return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
+ }
+ continue
+ }
+ if _, err := parse.ValidateDomain(dom); err != nil {
+ return nil, err
+ }
+ }
+ opts.DNSSearch = dnsSearches
}
m, err := cmd.Flags().GetString("mac-address")
@@ -85,6 +129,7 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
}
opts.StaticMAC = &mac
}
+
inputPorts, err := cmd.Flags().GetStringSlice("publish")
if err != nil {
return nil, err
@@ -95,6 +140,38 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
return nil, err
}
}
+
+ ip, err := cmd.Flags().GetString("ip")
+ if err != nil {
+ return nil, err
+ }
+ if ip != "" {
+ staticIP := net.ParseIP(ip)
+ if staticIP == nil {
+ return nil, errors.Errorf("%s is not an ip address", ip)
+ }
+ if staticIP.To4() == nil {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", ip)
+ }
+ opts.StaticIP = &staticIP
+ }
+
opts.NoHosts, err = cmd.Flags().GetBool("no-hosts")
+
+ if cmd.Flags().Changed("network") {
+ network, err := cmd.Flags().GetString("network")
+ if err != nil {
+ return nil, err
+ }
+
+ ns, cniNets, err := specgen.ParseNetworkNamespace(network)
+ if err != nil {
+ return nil, err
+ }
+
+ opts.Network = ns
+ opts.CNINetworks = cniNets
+ }
+
return &opts, err
}
diff --git a/cmd/podman/common/ports.go b/cmd/podman/common/ports.go
index 7e2b1e79d..2092bbe53 100644
--- a/cmd/podman/common/ports.go
+++ b/cmd/podman/common/ports.go
@@ -1,126 +1,22 @@
package common
import (
- "fmt"
- "net"
- "strconv"
-
- "github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
-// ExposedPorts parses user and image ports and returns binding information
-func ExposedPorts(expose []string, publish []ocicni.PortMapping, publishAll bool, imageExposedPorts map[string]struct{}) ([]ocicni.PortMapping, error) {
- containerPorts := make(map[string]string)
-
- // TODO this needs to be added into a something that
- // has access to an imageengine
- // add expose ports from the image itself
- //for expose := range imageExposedPorts {
- // _, port := nat.SplitProtoPort(expose)
- // containerPorts[port] = ""
- //}
-
+func verifyExpose(expose []string) error {
// add the expose ports from the user (--expose)
// can be single or a range
for _, expose := range expose {
- //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
+ // support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
_, port := nat.SplitProtoPort(expose)
- //parse the start and end port and create a sequence of ports to expose
- //if expose a port, the start and end port are the same
- start, end, err := nat.ParsePortRange(port)
+ // parse the start and end port and create a sequence of ports to expose
+ // if expose a port, the start and end port are the same
+ _, _, err := nat.ParsePortRange(port)
if err != nil {
- return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", expose, err)
- }
- for i := start; i <= end; i++ {
- containerPorts[strconv.Itoa(int(i))] = ""
- }
- }
-
- // TODO/FIXME this is hell reencarnated
- // parse user inputted port bindings
- pbPorts, portBindings, err := nat.ParsePortSpecs([]string{})
- if err != nil {
- return nil, err
- }
-
- // delete exposed container ports if being used by -p
- for i := range pbPorts {
- delete(containerPorts, i.Port())
- }
-
- // iterate container ports and make port bindings from them
- if publishAll {
- for e := range containerPorts {
- //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>]
- //proto, port := nat.SplitProtoPort(e)
- p, err := nat.NewPort("tcp", e)
- if err != nil {
- return nil, err
- }
- rp, err := getRandomPort()
- if err != nil {
- return nil, err
- }
- logrus.Debug(fmt.Sprintf("Using random host port %d with container port %d", rp, p.Int()))
- portBindings[p] = CreatePortBinding(rp, "")
- }
- }
-
- // We need to see if any host ports are not populated and if so, we need to assign a
- // random port to them.
- for k, pb := range portBindings {
- if pb[0].HostPort == "" {
- hostPort, err := getRandomPort()
- if err != nil {
- return nil, err
- }
- logrus.Debug(fmt.Sprintf("Using random host port %d with container port %s", hostPort, k.Port()))
- pb[0].HostPort = strconv.Itoa(hostPort)
- }
- }
- var pms []ocicni.PortMapping
- for k, v := range portBindings {
- for _, pb := range v {
- hp, err := strconv.Atoi(pb.HostPort)
- if err != nil {
- return nil, err
- }
- pms = append(pms, ocicni.PortMapping{
- HostPort: int32(hp),
- ContainerPort: int32(k.Int()),
- //Protocol: "",
- HostIP: pb.HostIP,
- })
+ return errors.Wrapf(err, "invalid range format for --expose: %s", expose)
}
}
- return pms, nil
-}
-
-func getRandomPort() (int, error) {
- l, err := net.Listen("tcp", ":0")
- if err != nil {
- return 0, errors.Wrapf(err, "unable to get free port")
- }
- defer l.Close()
- _, randomPort, err := net.SplitHostPort(l.Addr().String())
- if err != nil {
- return 0, errors.Wrapf(err, "unable to determine free port")
- }
- rp, err := strconv.Atoi(randomPort)
- if err != nil {
- return 0, errors.Wrapf(err, "unable to convert random port to int")
- }
- return rp, nil
-}
-
-//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs
-func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding {
- pb := nat.PortBinding{
- HostPort: strconv.Itoa(hostPort),
- }
- pb.HostIP = hostIP
- return []nat.PortBinding{pb}
+ return nil
}
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 87194a9fb..96cd630a3 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -1,7 +1,6 @@
package common
import (
- "encoding/json"
"fmt"
"os"
"path/filepath"
@@ -23,42 +22,135 @@ import (
"github.com/pkg/errors"
)
-func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
- var (
- err error
- //namespaces map[string]string
- )
+func getCPULimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxCPU, error) {
+ cpu := &specs.LinuxCPU{}
+ hasLimits := false
- // validate flags as needed
- if err := c.validate(); err != nil {
- return nil
+ if c.CPUShares > 0 {
+ cpu.Shares = &c.CPUShares
+ hasLimits = true
+ }
+ if c.CPUPeriod > 0 {
+ cpu.Period = &c.CPUPeriod
+ hasLimits = true
+ }
+ if c.CPUSetCPUs != "" {
+ cpu.Cpus = c.CPUSetCPUs
+ hasLimits = true
+ }
+ if c.CPUSetMems != "" {
+ cpu.Mems = c.CPUSetMems
+ hasLimits = true
+ }
+ if c.CPUQuota > 0 {
+ cpu.Quota = &c.CPUQuota
+ hasLimits = true
+ }
+ if c.CPURTPeriod > 0 {
+ cpu.RealtimePeriod = &c.CPURTPeriod
+ hasLimits = true
+ }
+ if c.CPURTRuntime > 0 {
+ cpu.RealtimeRuntime = &c.CPURTRuntime
+ hasLimits = true
}
- inputCommand := args[1:]
- if len(c.HealthCmd) > 0 {
- s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
+ if !hasLimits {
+ return nil, nil
+ }
+ return cpu, nil
+}
+
+func getIOLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxBlockIO, error) {
+ var err error
+ io := &specs.LinuxBlockIO{}
+ hasLimits := false
+ if b := c.BlkIOWeight; len(b) > 0 {
+ u, err := strconv.ParseUint(b, 10, 16)
if err != nil {
- return err
+ return nil, errors.Wrapf(err, "invalid value for blkio-weight")
}
+ nu := uint16(u)
+ io.Weight = &nu
+ hasLimits = true
}
- s.IDMappings, err = util.ParseIDMapping(ns.UsernsMode(c.UserNS), c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
- if err != nil {
- return err
+ if len(c.BlkIOWeightDevice) > 0 {
+ if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceReadBPs; len(bps) > 0 {
+ if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if bps := c.DeviceWriteBPs; len(bps) > 0 {
+ if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceReadIOPs; len(iops) > 0 {
+ if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if iops := c.DeviceWriteIOPs; len(iops) > 0 {
+ if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
+ return nil, err
+ }
+ hasLimits = true
+ }
+
+ if !hasLimits {
+ return nil, nil
+ }
+ return io, nil
+}
+
+func getPidsLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxPids, error) {
+ pids := &specs.LinuxPids{}
+ hasLimits := false
+ if c.CGroupsMode == "disabled" && c.PIDsLimit > 0 {
+ return nil, nil
+ }
+ if c.PIDsLimit > 0 {
+ pids.Limit = c.PIDsLimit
+ hasLimits = true
+ }
+ if !hasLimits {
+ return nil, nil
}
+ return pids, nil
+}
+
+func getMemoryLimits(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) (*specs.LinuxMemory, error) {
+ var err error
+ memory := &specs.LinuxMemory{}
+ hasLimits := false
if m := c.Memory; len(m) > 0 {
ml, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
- s.ResourceLimits.Memory.Limit = &ml
+ memory.Limit = &ml
+ hasLimits = true
}
if m := c.MemoryReservation; len(m) > 0 {
mr, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
- s.ResourceLimits.Memory.Reservation = &mr
+ memory.Reservation = &mr
+ hasLimits = true
}
if m := c.MemorySwap; len(m) > 0 {
var ms int64
@@ -68,99 +160,106 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
} else {
ms, err = units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for memory")
+ return nil, errors.Wrapf(err, "invalid value for memory")
}
}
- s.ResourceLimits.Memory.Swap = &ms
+ memory.Swap = &ms
+ hasLimits = true
}
if m := c.KernelMemory; len(m) > 0 {
mk, err := units.RAMInBytes(m)
if err != nil {
- return errors.Wrapf(err, "invalid value for kernel-memory")
+ return nil, errors.Wrapf(err, "invalid value for kernel-memory")
}
- s.ResourceLimits.Memory.Kernel = &mk
+ memory.Kernel = &mk
+ hasLimits = true
}
- if b := c.BlkIOWeight; len(b) > 0 {
- u, err := strconv.ParseUint(b, 10, 16)
+ if c.MemorySwappiness >= 0 {
+ swappiness := uint64(c.MemorySwappiness)
+ memory.Swappiness = &swappiness
+ hasLimits = true
+ }
+ if c.OOMKillDisable {
+ memory.DisableOOMKiller = &c.OOMKillDisable
+ hasLimits = true
+ }
+ if !hasLimits {
+ return nil, nil
+ }
+ return memory, nil
+}
+
+func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string) error {
+ var (
+ err error
+ // namespaces map[string]string
+ )
+
+ // validate flags as needed
+ if err := c.validate(); err != nil {
+ return nil
+ }
+
+ s.User = c.User
+ inputCommand := args[1:]
+ if len(c.HealthCmd) > 0 {
+ if c.NoHealthCheck {
+ return errors.New("Cannot specify both --no-healthcheck and --health-cmd")
+ }
+ s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod)
if err != nil {
- return errors.Wrapf(err, "invalid value for blkio-weight")
+ return err
+ }
+ } else if c.NoHealthCheck {
+ s.HealthConfig = &manifest.Schema2HealthConfig{
+ Test: []string{"NONE"},
}
- nu := uint16(u)
- s.ResourceLimits.BlockIO.Weight = &nu
}
- s.Terminal = c.TTY
- ep, err := ExposedPorts(c.Expose, c.Net.PublishPorts, c.PublishAll, nil)
+ userNS := ns.UsernsMode(c.UserNS)
+ s.IDMappings, err = util.ParseIDMapping(userNS, c.UIDMap, c.GIDMap, c.SubUIDName, c.SubGIDName)
if err != nil {
return err
}
- s.PortMappings = ep
+ // If some mappings are specified, assume a private user namespace
+ if userNS.IsDefaultValue() && (!s.IDMappings.HostUIDMapping || !s.IDMappings.HostGIDMapping) {
+ s.UserNS.NSMode = specgen.Private
+ }
+
+ s.Terminal = c.TTY
+
+ if err := verifyExpose(c.Expose); err != nil {
+ return err
+ }
+ // We are not handling the Expose flag yet.
+ // s.PortsExpose = c.Expose
+ s.PortMappings = c.Net.PublishPorts
+ s.PublishImagePorts = c.PublishAll
s.Pod = c.Pod
- //s.CgroupNS = specgen.Namespace{
- // NSMode: ,
- // Value: "",
- //}
-
- //s.UserNS = specgen.Namespace{}
-
- // 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
- //namespaces = map[string]string{
- // "cgroup": c.CGroupsNS,
- // "pid": c.PID,
- // //"net": c.Net.Network.Value, // TODO need help here
- // "ipc": c.IPC,
- // "user": c.User,
- // "uts": c.UTS,
- //}
- //
- //if len(c.PID) > 0 {
- // split := strings.SplitN(c.PID, ":", 2)
- // // need a way to do thsi
- // specgen.Namespace{
- // NSMode: split[0],
- // }
- // //Value: split1 if len allows
- //}
- // TODO this is going to have be done after things like pod creation are done because
- // pod creation changes these values.
- //pidMode := ns.PidMode(namespaces["pid"])
- //usernsMode := ns.UsernsMode(namespaces["user"])
- //utsMode := ns.UTSMode(namespaces["uts"])
- //cgroupMode := ns.CgroupMode(namespaces["cgroup"])
- //ipcMode := ns.IpcMode(namespaces["ipc"])
- //// 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")
- // }
- //}
-
- // TODO Remove when done with namespaces for realz
- // Setting a default for IPC to get this working
- s.IpcNS = specgen.Namespace{
- NSMode: specgen.Private,
- Value: "",
- }
-
- // TODO this is going to have to be done the libpod/server end of things
- // USER
- //user := c.String("user")
- //if user == "" {
- // switch {
- // case usernsMode.IsKeepID():
- // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
- // case data == nil:
- // user = "0"
- // default:
- // user = data.Config.User
- // }
- //}
+ for k, v := range map[string]*specgen.Namespace{
+ c.IPC: &s.IpcNS,
+ c.PID: &s.PidNS,
+ c.UTS: &s.UtsNS,
+ c.CGroupsNS: &s.CgroupNS,
+ } {
+ if k != "" {
+ *v, err = specgen.ParseNamespace(k)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // userns must be treated differently
+ if c.UserNS != "" {
+ s.UserNS, err = specgen.ParseUserNamespace(c.UserNS)
+ if err != nil {
+ return err
+ }
+ }
+ if c.Net != nil {
+ s.NetNS = c.Net.Network
+ }
// STOP SIGNAL
signalString := "TERM"
@@ -190,7 +289,23 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
if c.EnvHost {
env = envLib.Join(env, osEnv)
+ } else if c.HTTPProxy {
+ for _, envSpec := range []string{
+ "http_proxy",
+ "HTTP_PROXY",
+ "https_proxy",
+ "HTTPS_PROXY",
+ "ftp_proxy",
+ "FTP_PROXY",
+ "no_proxy",
+ "NO_PROXY",
+ } {
+ if v, ok := osEnv[envSpec]; ok {
+ env[envSpec] = v
+ }
+ }
}
+
// env-file overrides any previous variables
for _, f := range c.EnvFile {
fileEnv, err := envLib.ParseFile(f)
@@ -258,6 +373,8 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
var command []string
+ s.Entrypoint = entrypoint
+
// Build the command
// If we have an entry point, it goes first
if len(entrypoint) > 0 {
@@ -276,23 +393,27 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
// SHM Size
- shmSize, err := units.FromHumanSize(c.ShmSize)
- if err != nil {
- return errors.Wrapf(err, "unable to translate --shm-size")
+ if c.ShmSize != "" {
+ shmSize, err := units.FromHumanSize(c.ShmSize)
+ if err != nil {
+ return errors.Wrapf(err, "unable to translate --shm-size")
+ }
+ s.ShmSize = &shmSize
}
- s.ShmSize = &shmSize
s.HostAdd = c.Net.AddHosts
- s.DNSServer = c.Net.DNSServers
+ s.UseImageResolvConf = c.Net.UseImageResolvConf
+ s.DNSServers = c.Net.DNSServers
s.DNSSearch = c.Net.DNSSearch
- s.DNSOption = c.Net.DNSOptions
-
- // deferred, must be added on libpod side
- //var ImageVolumes map[string]struct{}
- //if data != nil && c.String("image-volume") != "ignore" {
- // ImageVolumes = data.Config.Volumes
- //}
+ s.DNSOptions = c.Net.DNSOptions
+ s.StaticIP = c.Net.StaticIP
+ s.StaticMAC = c.Net.StaticMAC
+ s.UseImageHosts = c.Net.NoHosts
s.ImageVolumeMode = c.ImageVolume
+ if s.ImageVolumeMode == "bind" {
+ s.ImageVolumeMode = "anonymous"
+ }
+
systemd := c.SystemdD == "always"
if !systemd && command != nil {
x, err := strconv.ParseBool(c.SystemdD)
@@ -312,14 +433,28 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.StopSignal = &stopSignal
}
}
- swappiness := uint64(c.MemorySwappiness)
if s.ResourceLimits == nil {
s.ResourceLimits = &specs.LinuxResources{}
}
- if s.ResourceLimits.Memory == nil {
- s.ResourceLimits.Memory = &specs.LinuxMemory{}
+ s.ResourceLimits.Memory, err = getMemoryLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.BlockIO, err = getIOLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.Pids, err = getPidsLimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ s.ResourceLimits.CPU, err = getCPULimits(s, c, args)
+ if err != nil {
+ return err
+ }
+ if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil {
+ s.ResourceLimits = nil
}
- s.ResourceLimits.Memory.Swappiness = &swappiness
if s.LogConfiguration == nil {
s.LogConfiguration = &specgen.LogConfig{}
@@ -328,35 +463,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
if ld := c.LogDriver; len(ld) > 0 {
s.LogConfiguration.Driver = ld
}
- if s.ResourceLimits.Pids == nil {
- s.ResourceLimits.Pids = &specs.LinuxPids{}
- }
- s.ResourceLimits.Pids.Limit = c.PIDsLimit
- if c.CGroups == "disabled" && c.PIDsLimit > 0 {
- s.ResourceLimits.Pids.Limit = -1
- }
- // TODO WTF
- //cgroup := &cc.CgroupConfig{
- // Cgroups: c.String("cgroups"),
- // Cgroupns: c.String("cgroupns"),
- // CgroupParent: c.String("cgroup-parent"),
- // CgroupMode: cgroupMode,
- //}
- //
- //userns := &cc.UserConfig{
- // GroupAdd: c.StringSlice("group-add"),
- // IDMappings: idmappings,
- // UsernsMode: usernsMode,
- // User: user,
- //}
- //
- //uts := &cc.UtsConfig{
- // UtsMode: utsMode,
- // NoHosts: c.Bool("no-hosts"),
- // HostAdd: c.StringSlice("add-host"),
- // Hostname: c.String("hostname"),
- //}
+ s.CgroupParent = c.CGroupParent
+ s.CgroupsMode = c.CGroupsMode
+ s.Groups = c.GroupAdd
+ s.Hostname = c.Hostname
sysctl := map[string]string{}
if ctl := c.Sysctl; len(ctl) > 0 {
sysctl, err = util.ValidateSysctls(ctl)
@@ -374,7 +485,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// TODO
// ouitside of specgen and oci though
// defaults to true, check spec/storage
- //s.readon = c.ReadOnlyTmpFS
+ // s.readon = c.ReadOnlyTmpFS
// TODO convert to map?
// check if key=value and convert
sysmap := make(map[string]string)
@@ -410,26 +521,31 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
}
}
- // TODO any idea why this was done
- // storage.go from spec/
- // grab it
- //volumes := rtc.Containers.Volumes
- // TODO conflict on populate?
- //if v := c.Volume; len(v)> 0 {
- // s.Volumes = append(volumes, c.StringSlice("volume")...)
- //}
- //s.volu
+ s.SeccompPolicy = c.SeccompPolicy
- //s.Mounts = c.Mount
+ // TODO: should parse out options
s.VolumesFrom = c.VolumesFrom
+ // Only add read-only tmpfs mounts in case that we are read-only and the
+ // read-only tmpfs flag has been set.
+ mounts, volumes, err := parseVolumes(c.Volume, c.Mount, c.TmpFS, c.ReadOnlyTmpFS && c.ReadOnly)
+ if err != nil {
+ return err
+ }
+ s.Mounts = mounts
+ s.Volumes = volumes
+
// TODO any idea why this was done
- //devices := rtc.Containers.Devices
+ // devices := rtc.Containers.Devices
// TODO conflict on populate?
//
- //if c.Changed("device") {
+ // if c.Changed("device") {
// devices = append(devices, c.StringSlice("device")...)
- //}
+ // }
+
+ for _, dev := range c.Devices {
+ s.Devices = append(s.Devices, specs.LinuxDevice{Path: dev})
+ }
// TODO things i cannot find in spec
// we dont think these are in the spec
@@ -437,33 +553,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
// initpath
s.Stdin = c.Interactive
// quiet
- //DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
-
- if bps := c.DeviceReadBPs; len(bps) > 0 {
- if s.ThrottleReadBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
- return err
- }
- }
-
- if bps := c.DeviceWriteBPs; len(bps) > 0 {
- if s.ThrottleWriteBpsDevice, err = parseThrottleBPSDevices(bps); err != nil {
- return err
- }
- }
-
- if iops := c.DeviceReadIOPs; len(iops) > 0 {
- if s.ThrottleReadIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
- return err
- }
- }
-
- if iops := c.DeviceWriteIOPs; len(iops) > 0 {
- if s.ThrottleWriteIOPSDevice, err = parseThrottleIOPsDevices(iops); err != nil {
- return err
- }
- }
-
- s.ResourceLimits.Memory.DisableOOMKiller = &c.OOMKillDisable
+ // DeviceCgroupRules: c.StringSlice("device-cgroup-rule"),
// Rlimits/Ulimits
for _, u := range c.Ulimit {
@@ -483,10 +573,10 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.Rlimits = append(s.Rlimits, rl)
}
- //Tmpfs: c.StringArray("tmpfs"),
+ // Tmpfs: c.StringArray("tmpfs"),
// TODO how to handle this?
- //Syslog: c.Bool("syslog"),
+ // Syslog: c.Bool("syslog"),
logOpts := make(map[string]string)
for _, o := range c.LogOptions {
@@ -494,37 +584,25 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
if len(split) < 2 {
return errors.Errorf("invalid log option %q", o)
}
- logOpts[split[0]] = split[1]
+ switch {
+ case split[0] == "driver":
+ s.LogConfiguration.Driver = split[1]
+ case split[0] == "path":
+ s.LogConfiguration.Path = split[1]
+ default:
+ logOpts[split[0]] = split[1]
+ }
}
s.LogConfiguration.Options = logOpts
s.Name = c.Name
- if err := parseWeightDevices(c.BlkIOWeightDevice, s); err != nil {
- return err
- }
-
- if s.ResourceLimits.CPU == nil {
- s.ResourceLimits.CPU = &specs.LinuxCPU{}
- }
- s.ResourceLimits.CPU.Shares = &c.CPUShares
- s.ResourceLimits.CPU.Period = &c.CPUPeriod
-
- // TODO research these
- //s.ResourceLimits.CPU.Cpus = c.CPUS
- //s.ResourceLimits.CPU.Cpus = c.CPUSetCPUs
-
- //s.ResourceLimits.CPU. = c.CPUSetCPUs
- s.ResourceLimits.CPU.Mems = c.CPUSetMems
- s.ResourceLimits.CPU.Quota = &c.CPUQuota
- s.ResourceLimits.CPU.RealtimePeriod = &c.CPURTPeriod
- s.ResourceLimits.CPU.RealtimeRuntime = &c.CPURTRuntime
s.OOMScoreAdj = &c.OOMScoreAdj
s.RestartPolicy = c.Restart
s.Remove = c.Rm
s.StopTimeout = &c.StopTimeout
// TODO where should we do this?
- //func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
+ // func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) {
return nil
}
@@ -536,10 +614,15 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start
// first try to parse option value as JSON array of strings...
cmd := []string{}
- err := json.Unmarshal([]byte(inCmd), &cmd)
- if err != nil {
- // ...otherwise pass it to "/bin/sh -c" inside the container
- cmd = []string{"CMD-SHELL", inCmd}
+
+ if inCmd == "none" {
+ cmd = []string{"NONE"}
+ } else {
+ err := json.Unmarshal([]byte(inCmd), &cmd)
+ if err != nil {
+ // ...otherwise pass it to "/bin/sh -c" inside the container
+ cmd = []string{"CMD-SHELL", inCmd}
+ }
}
hc := manifest.Schema2HealthConfig{
Test: cmd,
diff --git a/cmd/podman/common/volumes.go b/cmd/podman/common/volumes.go
new file mode 100644
index 000000000..6b0b6e9cf
--- /dev/null
+++ b/cmd/podman/common/volumes.go
@@ -0,0 +1,569 @@
+package common
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/util"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // TypeBind is the type for mounting host dir
+ TypeBind = "bind"
+ // TypeVolume is the type for named volumes
+ TypeVolume = "volume"
+ // TypeTmpfs is the type for mounting tmpfs
+ TypeTmpfs = "tmpfs"
+)
+
+var (
+ errDuplicateDest = errors.Errorf("duplicate mount destination")
+ optionArgError = errors.Errorf("must provide an argument for option")
+ noDestError = errors.Errorf("must set volume destination")
+)
+
+// Parse all volume-related options in the create config into a set of mounts
+// and named volumes to add to the container.
+// Handles --volumes, --mount, and --tmpfs flags.
+// Does not handle image volumes, init, and --volumes-from flags.
+// Can also add tmpfs mounts from read-only tmpfs.
+// TODO: handle options parsing/processing via containers/storage/pkg/mount
+func parseVolumes(volumeFlag, mountFlag, tmpfsFlag []string, addReadOnlyTmpfs bool) ([]spec.Mount, []*specgen.NamedVolume, error) {
+ // Get mounts from the --mounts flag.
+ unifiedMounts, unifiedVolumes, err := getMounts(mountFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --volumes flag.
+ volumeMounts, volumeVolumes, err := getVolumeMounts(volumeFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Next --tmpfs flag.
+ tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // Unify mounts from --mount, --volume, --tmpfs.
+ // Start with --volume.
+ for dest, mount := range volumeMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = mount
+ }
+ for dest, volume := range volumeVolumes {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedVolumes[dest] = volume
+ }
+ // Now --tmpfs
+ for dest, tmpfs := range tmpfsMounts {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, dest)
+ }
+ unifiedMounts[dest] = tmpfs
+ }
+
+ // If requested, add tmpfs filesystems for read-only containers.
+ if addReadOnlyTmpfs {
+ readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
+ options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
+ for _, dest := range readonlyTmpfs {
+ if _, ok := unifiedMounts[dest]; ok {
+ continue
+ }
+ if _, ok := unifiedVolumes[dest]; ok {
+ continue
+ }
+ localOpts := options
+ if dest == "/run" {
+ localOpts = append(localOpts, "noexec", "size=65536k")
+ } else {
+ localOpts = append(localOpts, "exec")
+ }
+ unifiedMounts[dest] = spec.Mount{
+ Destination: dest,
+ Type: TypeTmpfs,
+ Source: "tmpfs",
+ Options: localOpts,
+ }
+ }
+ }
+
+ // Check for conflicts between named volumes and mounts
+ for dest := range unifiedMounts {
+ if _, ok := unifiedVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+ for dest := range unifiedVolumes {
+ if _, ok := unifiedMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
+ for _, mount := range unifiedMounts {
+ if mount.Type == TypeBind {
+ absSrc, err := filepath.Abs(mount.Source)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
+ }
+ mount.Source = absSrc
+ }
+ finalMounts = append(finalMounts, mount)
+ }
+ finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes))
+ for _, volume := range unifiedVolumes {
+ finalVolumes = append(finalVolumes, volume)
+ }
+
+ return finalMounts, finalVolumes, nil
+}
+
+// getMounts takes user-provided input from the --mount flag and creates OCI
+// spec mounts and Libpod named volumes.
+// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
+// podman run --mount type=tmpfs,target=/dev/shm ...
+// podman run --mount type=volume,source=test-volume, ...
+func getMounts(mountFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*specgen.NamedVolume)
+
+ errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+
+ // TODO(vrothberg): the manual parsing can be replaced with a regular expression
+ // to allow a more robust parsing of the mount format and to give
+ // precise errors regarding supported format versus supported options.
+ for _, mount := range mountFlag {
+ arr := strings.SplitN(mount, ",", 2)
+ if len(arr) < 2 {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+ kv := strings.Split(arr[0], "=")
+ // TODO: type is not explicitly required in Docker.
+ // If not specified, it defaults to "volume".
+ if len(kv) != 2 || kv[0] != "type" {
+ return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ }
+
+ tokens := strings.Split(arr[1], ",")
+ switch kv[1] {
+ case TypeBind:
+ mount, err := getBindMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case TypeTmpfs:
+ mount, err := getTmpfsMount(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalMounts[mount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
+ }
+ finalMounts[mount.Destination] = mount
+ case "volume":
+ volume, err := getNamedVolume(tokens)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := finalNamedVolumes[volume.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
+ }
+ finalNamedVolumes[volume.Dest] = volume
+ default:
+ return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
+ }
+ }
+
+ return finalMounts, finalNamedVolumes, nil
+}
+
+// Parse a single bind mount entry from the --mount flag.
+func getBindMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeBind,
+ }
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "bind-nonrecursive":
+ newMount.Options = append(newMount.Options, "bind")
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once")
+ }
+ setRORW = true
+ // Can be formatted as one of:
+ // ro
+ // ro=[true|false]
+ // rw
+ // rw=[true|false]
+ switch len(kv) {
+ case 1:
+ newMount.Options = append(newMount.Options, kv[0])
+ case 2:
+ switch strings.ToLower(kv[1]) {
+ case "true":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "false":
+ // Set the opposite only for rw
+ // ro's opposite is the default
+ if kv[0] == "rw" {
+ newMount.Options = append(newMount.Options, "ro")
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1])
+ }
+ default:
+ return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
+ }
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
+ newMount.Options = append(newMount.Options, kv[0])
+ case "bind-propagation":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, kv[1])
+ case "src", "source":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeHostDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Source = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ case "relabel":
+ if setRelabel {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
+ }
+ setRelabel = true
+ if len(kv) != 2 {
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ switch kv[1] {
+ case "private":
+ newMount.Options = append(newMount.Options, "z")
+ case "shared":
+ newMount.Options = append(newMount.Options, "Z")
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
+ }
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ if !setSource {
+ newMount.Source = newMount.Destination
+ }
+
+ options, err := parse.ValidateVolumeOpts(newMount.Options)
+ if err != nil {
+ return newMount, err
+ }
+ newMount.Options = options
+ return newMount, nil
+}
+
+// Parse a single tmpfs mount entry from the --mount flag
+func getTmpfsMount(args []string) (spec.Mount, error) {
+ newMount := spec.Mount{
+ Type: TypeTmpfs,
+ Source: TypeTmpfs,
+ }
+
+ var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "tmpcopyup", "notmpcopyup":
+ if setTmpcopyup {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
+ }
+ setTmpcopyup = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "ro", "rw":
+ if setRORW {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newMount.Options = append(newMount.Options, kv[0])
+ case "tmpfs-mode":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
+ case "tmpfs-size":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
+ case "src", "source":
+ return newMount, errors.Errorf("source is not supported with tmpfs mounts")
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return newMount, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return newMount, err
+ }
+ newMount.Destination = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setDest {
+ return newMount, noDestError
+ }
+
+ return newMount, nil
+}
+
+// Parse a single volume mount entry from the --mount flag.
+// Note that the volume-label option for named volumes is currently NOT supported.
+// TODO: add support for --volume-label
+func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
+ newVolume := new(specgen.NamedVolume)
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ switch kv[0] {
+ case "ro", "rw":
+ if setRORW {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
+ }
+ setRORW = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nosuid", "suid":
+ if setSuid {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
+ }
+ setSuid = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "nodev", "dev":
+ if setDev {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
+ }
+ setDev = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "noexec", "exec":
+ if setExec {
+ return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
+ }
+ setExec = true
+ newVolume.Options = append(newVolume.Options, kv[0])
+ case "volume-label":
+ return nil, errors.Errorf("the --volume-label option is not presently implemented")
+ case "src", "source":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ newVolume.Name = kv[1]
+ setSource = true
+ case "target", "dst", "destination":
+ if len(kv) == 1 {
+ return nil, errors.Wrapf(optionArgError, kv[0])
+ }
+ if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
+ return nil, err
+ }
+ newVolume.Dest = filepath.Clean(kv[1])
+ setDest = true
+ default:
+ return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ }
+ }
+
+ if !setSource {
+ return nil, errors.Errorf("must set source volume")
+ }
+ if !setDest {
+ return nil, noDestError
+ }
+
+ return newVolume, nil
+}
+
+func getVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*specgen.NamedVolume)
+
+ volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
+
+ for _, vol := range volumeFlag {
+ var (
+ options []string
+ src string
+ dest string
+ err error
+ )
+
+ splitVol := strings.Split(vol, ":")
+ if len(splitVol) > 3 {
+ return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ }
+
+ src = splitVol[0]
+ if len(splitVol) == 1 {
+ // This is an anonymous named volume. Only thing given
+ // is destination.
+ // Name/source will be blank, and populated by libpod.
+ src = ""
+ dest = splitVol[0]
+ } else if len(splitVol) > 1 {
+ dest = splitVol[1]
+ }
+ if len(splitVol) > 2 {
+ if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
+ return nil, nil, err
+ }
+ }
+
+ // Do not check source dir for anonymous volumes
+ if len(splitVol) > 1 {
+ if err := parse.ValidateVolumeHostDir(src); err != nil {
+ return nil, nil, err
+ }
+ }
+ if err := parse.ValidateVolumeCtrDir(dest); err != nil {
+ return nil, nil, err
+ }
+
+ cleanDest := filepath.Clean(dest)
+
+ if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
+ // This is not a named volume
+ newMount := spec.Mount{
+ Destination: cleanDest,
+ Type: string(TypeBind),
+ Source: src,
+ Options: options,
+ }
+ if _, ok := mounts[newMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
+ }
+ mounts[newMount.Destination] = newMount
+ } else {
+ // This is a named volume
+ newNamedVol := new(specgen.NamedVolume)
+ newNamedVol.Name = src
+ newNamedVol.Dest = cleanDest
+ newNamedVol.Options = options
+
+ if _, ok := volumes[newNamedVol.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ }
+ volumes[newNamedVol.Dest] = newNamedVol
+ }
+
+ logrus.Debugf("User mount %s:%s options %v", src, dest, options)
+ }
+
+ return mounts, volumes, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
+ m := make(map[string]spec.Mount)
+ for _, i := range tmpfsFlag {
+ // Default options if nothing passed
+ var options []string
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
+ return nil, err
+ }
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+
+ if _, ok := m[destPath]; ok {
+ return nil, errors.Wrapf(errDuplicateDest, destPath)
+ }
+
+ mount := spec.Mount{
+ Destination: filepath.Clean(destPath),
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ }
+ m[destPath] = mount
+ }
+ return m, nil
+}
diff --git a/cmd/podman/containers/attach.go b/cmd/podman/containers/attach.go
index 700be1f84..ee4d811d7 100644
--- a/cmd/podman/containers/attach.go
+++ b/cmd/podman/containers/attach.go
@@ -3,11 +3,11 @@ package containers
import (
"os"
- "github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -27,19 +27,24 @@ var (
podman attach 1234
podman attach --no-stdin foobar`,
}
+
+ containerAttachCommand = &cobra.Command{
+ Use: attachCommand.Use,
+ Short: attachCommand.Short,
+ Long: attachCommand.Long,
+ RunE: attachCommand.RunE,
+ Example: `podman container attach ctrID
+ podman container attach 1234
+ podman container attach --no-stdin foobar`,
+ }
)
var (
attachOpts entities.AttachOptions
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: attachCommand,
- })
- flags := attachCommand.Flags()
- flags.StringVar(&attachOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`")
+func attachFlags(flags *pflag.FlagSet) {
+ flags.StringVar(&attachOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`")
flags.BoolVar(&attachOpts.NoStdin, "no-stdin", false, "Do not attach STDIN. The default is false")
flags.BoolVar(&attachOpts.SigProxy, "sig-proxy", true, "Proxy received signals to the process")
flags.BoolVarP(&attachOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
@@ -48,12 +53,36 @@ func init() {
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: attachCommand,
+ })
+ flags := attachCommand.Flags()
+ attachFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerAttachCommand,
+ Parent: containerCmd,
+ })
+ containerAttachFlags := containerAttachCommand.Flags()
+ attachFlags(containerAttachFlags)
+}
+
func attach(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || (len(args) == 0 && !attachOpts.Latest) {
+ return errors.Errorf("attach requires the name or id of one running container or the latest flag")
+ }
+ var name string
+ if len(args) > 0 {
+ name = args[0]
+ }
attachOpts.Stdin = os.Stdin
if attachOpts.NoStdin {
attachOpts.Stdin = nil
}
attachOpts.Stdout = os.Stdout
attachOpts.Stderr = os.Stderr
- return registry.ContainerEngine().ContainerAttach(registry.GetContext(), args[0], attachOpts)
+ return registry.ContainerEngine().ContainerAttach(registry.GetContext(), name, attachOpts)
}
diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go
index eaba07981..137e486eb 100644
--- a/cmd/podman/containers/commit.go
+++ b/cmd/podman/containers/commit.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -28,6 +29,17 @@ var (
podman commit containerID`,
}
+ containerCommitCommand = &cobra.Command{
+ Use: commitCommand.Use,
+ Short: commitCommand.Short,
+ Long: commitCommand.Long,
+ RunE: commitCommand.RunE,
+ Example: `podman container commit -q --message "committing container to image" reverent_golick image-committed
+ podman container commit -q --author "firstName lastName" reverent_golick image-committed
+ podman container commit -q --pause=false containerID image-committed
+ podman container commit containerID`,
+ }
+
// ChangeCmds is the list of valid Changes commands to passed to the Commit call
ChangeCmds = []string{"CMD", "ENTRYPOINT", "ENV", "EXPOSE", "LABEL", "ONBUILD", "STOPSIGNAL", "USER", "VOLUME", "WORKDIR"}
)
@@ -39,12 +51,7 @@ var (
iidFile string
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: commitCommand,
- })
- flags := commitCommand.Flags()
+func commitFlags(flags *pflag.FlagSet) {
flags.StringArrayVarP(&commitOptions.Changes, "change", "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(ChangeCmds, " | "))
flags.StringVarP(&commitOptions.Format, "format", "f", "oci", "`Format` of the image manifest and metadata")
flags.StringVarP(&iidFile, "iidfile", "", "", "`file` to write the image ID to")
@@ -53,8 +60,25 @@ func init() {
flags.BoolVarP(&commitOptions.Pause, "pause", "p", false, "Pause container during commit")
flags.BoolVarP(&commitOptions.Quiet, "quiet", "q", false, "Suppress output")
flags.BoolVar(&commitOptions.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes")
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: commitCommand,
+ })
+ flags := commitCommand.Flags()
+ commitFlags(flags)
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerCommitCommand,
+ Parent: containerCmd,
+ })
+ containerCommitFlags := containerCommitCommand.Flags()
+ commitFlags(containerCommitFlags)
}
+
func commit(cmd *cobra.Command, args []string) error {
container := args[0]
if len(args) > 1 {
diff --git a/cmd/podman/containers/container.go b/cmd/podman/containers/container.go
index 8564b23f4..a102318fb 100644
--- a/cmd/podman/containers/container.go
+++ b/cmd/podman/containers/container.go
@@ -1,26 +1,27 @@
package containers
import (
- "os"
-
- "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
- "github.com/sirupsen/logrus"
+ "github.com/containers/libpod/pkg/util"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _container_
containerCmd = &cobra.Command{
Use: "container",
Short: "Manage containers",
Long: "Manage containers",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
- defaultContainerConfig = getDefaultContainerConfig()
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
@@ -29,12 +30,3 @@ func init() {
Command: containerCmd,
})
}
-
-func getDefaultContainerConfig() *config.Config {
- defaultContainerConfig, err := config.Default()
- if err != nil {
- logrus.Error(err)
- os.Exit(1)
- }
- return defaultContainerConfig
-}
diff --git a/cmd/podman/containers/cp.go b/cmd/podman/containers/cp.go
new file mode 100644
index 000000000..f0f9a158d
--- /dev/null
+++ b/cmd/podman/containers/cp.go
@@ -0,0 +1,55 @@
+package containers
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/cobra"
+)
+
+var (
+ 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,
+ Args: cobra.ExactArgs(2),
+ RunE: cp,
+ Example: "podman cp [CONTAINER:]SRC_PATH [CONTAINER:]DEST_PATH",
+ }
+)
+
+var (
+ cpOpts entities.ContainerCpOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: cpCommand,
+ })
+ flags := cpCommand.Flags()
+ flags.BoolVar(&cpOpts.Extract, "extract", false, "Extract the tar file into the destination directory.")
+ flags.BoolVar(&cpOpts.Pause, "pause", copyPause(), "Pause the container while copying")
+}
+
+func cp(cmd *cobra.Command, args []string) error {
+ _, err := registry.ContainerEngine().ContainerCp(registry.GetContext(), args[0], args[1], cpOpts)
+ return err
+}
+
+func copyPause() bool {
+ if rootless.IsRootless() {
+ cgroupv2, _ := cgroups.IsCgroup2UnifiedMode()
+ if !cgroupv2 {
+ logrus.Debugf("defaulting to pause==false on rootless cp in cgroupv1 systems")
+ return false
+ }
+ }
+ return true
+}
diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go
index 292d5c1ad..3e47a8b4f 100644
--- a/cmd/podman/containers/create.go
+++ b/cmd/podman/containers/create.go
@@ -2,14 +2,19 @@ package containers
import (
"fmt"
+ "os"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -26,43 +31,76 @@ var (
podman create --annotation HELLO=WORLD alpine ls
podman create -t -i --name myctr alpine ls`,
}
+
+ containerCreateCommand = &cobra.Command{
+ Use: createCommand.Use,
+ Short: createCommand.Short,
+ Long: createCommand.Long,
+ RunE: createCommand.RunE,
+ Example: `podman container create alpine ls
+ podman container create --annotation HELLO=WORLD alpine ls
+ podman container create -t -i --name myctr alpine ls`,
+ }
)
var (
cliVals common.ContainerCLIOpts
)
+func createFlags(flags *pflag.FlagSet) {
+ flags.SetInterspersed(false)
+ flags.AddFlagSet(common.GetCreateFlags(&cliVals))
+ flags.AddFlagSet(common.GetNetFlags())
+ flags.SetNormalizeFunc(common.AliasFlags)
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: createCommand,
})
- //common.GetCreateFlags(createCommand)
+ // common.GetCreateFlags(createCommand)
flags := createCommand.Flags()
- flags.SetInterspersed(false)
- flags.AddFlagSet(common.GetCreateFlags(&cliVals))
- flags.AddFlagSet(common.GetNetFlags())
- flags.SetNormalizeFunc(common.AliasFlags)
+ createFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerCreateCommand,
+ Parent: containerCmd,
+ })
+
+ containerCreateFlags := containerCreateCommand.Flags()
+ createFlags(containerCreateFlags)
}
func create(cmd *cobra.Command, args []string) error {
var (
- err error
- rawImageInput string
+ err error
)
cliVals.Net, err = common.NetFlagsToNetOptions(cmd)
if err != nil {
return err
}
- if rfs := cliVals.RootFS; !rfs {
- rawImageInput = args[0]
+ cidFile, err := openCidFile(cliVals.CIDFile)
+ if err != nil {
+ return err
+ }
+
+ if cidFile != nil {
+ defer errorhandling.CloseQuiet(cidFile)
+ defer errorhandling.SyncQuiet(cidFile)
}
if err := createInit(cmd); err != nil {
return err
}
- //TODO rootfs still
- s := specgen.NewSpecGenerator(rawImageInput)
+
+ if !cliVals.RootFS {
+ if err := pullImage(args[0]); err != nil {
+ return err
+ }
+ }
+ s := specgen.NewSpecGenerator(args[0], cliVals.RootFS)
if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
}
@@ -71,6 +109,14 @@ func create(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+
+ if cidFile != nil {
+ _, err = cidFile.WriteString(report.Id)
+ if err != nil {
+ logrus.Error(err)
+ }
+ }
+
fmt.Println(report.Id)
return nil
}
@@ -80,6 +126,10 @@ func createInit(c *cobra.Command) error {
logrus.Warn("setting security options with --privileged has no effect")
}
+ if c.Flag("shm-size").Changed {
+ cliVals.ShmSize = c.Flag("shm-size").Value.String()
+ }
+
if (c.Flag("dns").Changed || c.Flag("dns-opt").Changed || c.Flag("dns-search").Changed) && (cliVals.Net.Network.NSMode == specgen.NoNetwork || cliVals.Net.Network.IsContainer()) {
return errors.Errorf("conflicting options: dns and the network mode.")
}
@@ -94,9 +144,62 @@ func createInit(c *cobra.Command) error {
if c.Flag("no-hosts").Changed && c.Flag("add-host").Changed {
return errors.Errorf("--no-hosts and --add-host cannot be set together")
}
+ if c.Flag("userns").Changed {
+ cliVals.UserNS = c.Flag("userns").Value.String()
+ }
+ if c.Flag("ipc").Changed {
+ cliVals.IPC = c.Flag("ipc").Value.String()
+ }
+ if c.Flag("uts").Changed {
+ cliVals.UTS = c.Flag("uts").Value.String()
+ }
+ if c.Flag("pid").Changed {
+ cliVals.PID = c.Flag("pid").Value.String()
+ }
+ if c.Flag("cgroupns").Changed {
+ cliVals.CGroupsNS = c.Flag("cgroupns").Value.String()
+ }
// Docker-compatibility: the "-h" flag for run/create is reserved for
// the hostname (see https://github.com/containers/libpod/issues/1367).
return nil
}
+
+func pullImage(imageName string) error {
+ br, err := registry.ImageEngine().Exists(registry.GetContext(), imageName)
+ if err != nil {
+ return err
+ }
+ pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull)
+ if err != nil {
+ return err
+ }
+ if !br.Value || pullPolicy == config.PullImageAlways {
+ if pullPolicy == config.PullImageNever {
+ return errors.New("unable to find a name and tag match for busybox in repotags: no such image")
+ }
+ _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{
+ Authfile: cliVals.Authfile,
+ Quiet: cliVals.Quiet,
+ })
+ if pullErr != nil {
+ return pullErr
+ }
+ }
+ return nil
+}
+
+func openCidFile(cidfile string) (*os.File, error) {
+ if cidfile == "" {
+ return nil, nil
+ }
+ cidFile, err := util.OpenExclusiveFile(cidfile)
+ if err != nil && os.IsExist(err) {
+ return nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", cidfile)
+ }
+ if err != nil {
+ return nil, errors.Errorf("error opening cidfile %s", cidfile)
+ }
+ return cidFile, nil
+}
diff --git a/cmd/podman/containers/diff.go b/cmd/podman/containers/diff.go
index ebc0d8ea1..59b788010 100644
--- a/cmd/podman/containers/diff.go
+++ b/cmd/podman/containers/diff.go
@@ -3,6 +3,7 @@ package containers
import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/report"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -12,7 +13,7 @@ var (
// podman container _diff_
diffCmd = &cobra.Command{
Use: "diff [flags] CONTAINER",
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Short: "Inspect changes on container's file systems",
Long: `Displays changes on a container filesystem. The container will be compared to its parent layer.`,
RunE: diff,
@@ -45,7 +46,11 @@ func diff(cmd *cobra.Command, args []string) error {
return errors.New("container must be specified: podman container diff [options [...]] ID-NAME")
}
- results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), args[0], entities.DiffOptions{})
+ var id string
+ if len(args) > 0 {
+ id = args[0]
+ }
+ results, err := registry.ContainerEngine().ContainerDiff(registry.GetContext(), id, *diffOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/containers/exec.go b/cmd/podman/containers/exec.go
index 68ecb2196..2bff8ae33 100644
--- a/cmd/podman/containers/exec.go
+++ b/cmd/podman/containers/exec.go
@@ -4,12 +4,12 @@ import (
"bufio"
"os"
- "github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
envLib "github.com/containers/libpod/pkg/env"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -24,6 +24,16 @@ var (
podman exec -it -w /tmp myCtr pwd
podman exec --user root ctrID ls`,
}
+
+ containerExecCommand = &cobra.Command{
+ Use: execCommand.Use,
+ Short: execCommand.Short,
+ Long: execCommand.Long,
+ RunE: execCommand.RunE,
+ Example: `podman container exec -it ctrID ls
+ podman container exec -it -w /tmp myCtr pwd
+ podman container exec --user root ctrID ls`,
+ }
)
var (
@@ -31,14 +41,9 @@ var (
execOpts entities.ExecOptions
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: execCommand,
- })
- flags := execCommand.Flags()
+func execFlags(flags *pflag.FlagSet) {
flags.SetInterspersed(false)
- flags.StringVar(&execOpts.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select 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.StringVar(&execOpts.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select 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.StringArrayVarP(&envInput, "env", "e", []string{}, "Set environment variables")
flags.StringSliceVar(&envFile, "env-file", []string{}, "Read in a file of environment variables")
flags.BoolVarP(&execOpts.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
@@ -52,8 +57,26 @@ func init() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("preserve-fds")
}
+}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: execCommand,
+ })
+ flags := execCommand.Flags()
+ execFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerExecCommand,
+ Parent: containerCmd,
+ })
+
+ containerExecFlags := containerExecCommand.Flags()
+ execFlags(containerExecFlags)
}
+
func exec(cmd *cobra.Command, args []string) error {
var nameOrId string
execOpts.Cmd = args
diff --git a/cmd/podman/containers/exists.go b/cmd/podman/containers/exists.go
index f1bc09f78..81ba8a282 100644
--- a/cmd/podman/containers/exists.go
+++ b/cmd/podman/containers/exists.go
@@ -2,7 +2,6 @@ package containers
import (
"context"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -18,8 +17,9 @@ var (
Long: containerExistsDescription,
Example: `podman container exists containerID
podman container exists myctr || podman run --name myctr [etc...]`,
- RunE: exists,
- Args: cobra.ExactArgs(1),
+ RunE: exists,
+ Args: cobra.ExactArgs(1),
+ DisableFlagsInUseLine: true,
}
)
@@ -37,7 +37,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !response.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/containers/export.go b/cmd/podman/containers/export.go
index 5110812d1..fb5bd468f 100644
--- a/cmd/podman/containers/export.go
+++ b/cmd/podman/containers/export.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
)
@@ -25,19 +26,41 @@ var (
Example: `podman export ctrID > myCtr.tar
podman export --output="myCtr.tar" ctrID`,
}
+
+ containerExportCommand = &cobra.Command{
+ Use: exportCommand.Use,
+ Short: exportCommand.Short,
+ Long: exportCommand.Long,
+ RunE: exportCommand.RunE,
+ Example: `podman container export ctrID > myCtr.tar
+ podman container export --output="myCtr.tar" ctrID`,
+ }
)
var (
exportOpts entities.ContainerExportOptions
)
+func exportFlags(flags *pflag.FlagSet) {
+ flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)")
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: exportCommand,
})
flags := exportCommand.Flags()
- flags.StringVarP(&exportOpts.Output, "output", "o", "", "Write to a specified file (default: stdout, which must be redirected)")
+ exportFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerExportCommand,
+ Parent: containerCmd,
+ })
+
+ containerExportFlags := containerExportCommand.Flags()
+ exportFlags(containerExportFlags)
}
func export(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go
index 8d591832b..4549a4ef6 100644
--- a/cmd/podman/containers/inspect.go
+++ b/cmd/podman/containers/inspect.go
@@ -1,17 +1,9 @@
package containers
import (
- "context"
- "fmt"
- "os"
- "strings"
- "text/template"
-
- "github.com/containers/libpod/cmd/podman/common"
+ "github.com/containers/libpod/cmd/podman/inspect"
"github.com/containers/libpod/cmd/podman/registry"
-
"github.com/containers/libpod/pkg/domain/entities"
- json "github.com/json-iterator/go"
"github.com/spf13/cobra"
)
@@ -21,7 +13,7 @@ var (
Use: "inspect [flags] CONTAINER",
Short: "Display the configuration of a container",
Long: `Displays the low-level information on a container identified by name or ID.`,
- RunE: inspect,
+ RunE: inspectExec,
Example: `podman container inspect myCtr
podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`,
}
@@ -34,45 +26,9 @@ func init() {
Command: inspectCmd,
Parent: containerCmd,
})
- inspectOpts = common.AddInspectFlagSet(inspectCmd)
- flags := inspectCmd.Flags()
-
- if !registry.IsRemote() {
- flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
- }
-
-}
-
-func inspect(cmd *cobra.Command, args []string) error {
- responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts)
- if err != nil {
- return err
- }
- if inspectOpts.Format == "" {
- b, err := json.MarshalIndent(responses, "", " ")
- if err != nil {
- return err
- }
- fmt.Println(string(b))
- return nil
- }
- format := inspectOpts.Format
- if !strings.HasSuffix(format, "\n") {
- format += "\n"
- }
- tmpl, err := template.New("inspect").Parse(format)
- if err != nil {
- return err
- }
- for _, i := range responses {
- if err := tmpl.Execute(os.Stdout, i); err != nil {
- return err
- }
- }
- return nil
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
}
-func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error {
- inspectOpts = options
- return inspect(cmd, args)
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/containers/kill.go b/cmd/podman/containers/kill.go
index 5341457fb..8b4a384fe 100644
--- a/cmd/podman/containers/kill.go
+++ b/cmd/podman/containers/kill.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -27,18 +28,23 @@ var (
podman kill 860a4b23
podman kill --signal TERM ctrID`,
}
+
+ containerKillCommand = &cobra.Command{
+ Use: killCommand.Use,
+ Short: killCommand.Short,
+ Long: killCommand.Long,
+ RunE: killCommand.RunE,
+ Example: `podman container kill mywebserver
+ podman container kill 860a4b23
+ podman container kill --signal TERM ctrID`,
+ }
)
var (
killOptions = entities.KillOptions{}
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: killCommand,
- })
- flags := killCommand.Flags()
+func killFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers")
flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container")
flags.BoolVarP(&killOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
@@ -47,6 +53,24 @@ func init() {
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: killCommand,
+ })
+ flags := killCommand.Flags()
+ killFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerKillCommand,
+ Parent: containerCmd,
+ })
+
+ containerKillFlags := containerKillCommand.Flags()
+ killFlags(containerKillFlags)
+}
+
func kill(cmd *cobra.Command, args []string) error {
var (
err error
diff --git a/cmd/podman/containers/list.go b/cmd/podman/containers/list.go
index 938fb63d3..c200a49aa 100644
--- a/cmd/podman/containers/list.go
+++ b/cmd/podman/containers/list.go
@@ -2,6 +2,7 @@ package containers
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -11,10 +12,10 @@ var (
listCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "List containers",
Long: "Prints out information about the containers",
- RunE: containers,
+ RunE: ps,
Example: `podman container list -a
podman container list -a --format "{{.ID}} {{.Image}} {{.Labels}} {{.Mounts}}"
podman container list --size --sort names`,
@@ -27,8 +28,5 @@ func init() {
Command: listCmd,
Parent: containerCmd,
})
-}
-
-func containers(cmd *cobra.Command, args []string) error {
- return nil
+ listFlagSet(listCmd.Flags())
}
diff --git a/cmd/podman/containers/mount.go b/cmd/podman/containers/mount.go
index 25eec46ca..0bdac72cb 100644
--- a/cmd/podman/containers/mount.go
+++ b/cmd/podman/containers/mount.go
@@ -1,7 +1,6 @@
package containers
import (
- "encoding/json"
"fmt"
"os"
"text/tabwriter"
@@ -12,6 +11,7 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -34,22 +34,41 @@ var (
registry.ParentNSRequired: "",
},
}
+
+ containerMountCommmand = &cobra.Command{
+ Use: mountCommand.Use,
+ Short: mountCommand.Short,
+ Long: mountCommand.Long,
+ RunE: mountCommand.RunE,
+ }
)
var (
mountOpts entities.ContainerMountOptions
)
+func mountFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers")
+ flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template")
+ flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output")
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: mountCommand,
})
flags := mountCommand.Flags()
- flags.BoolVarP(&mountOpts.All, "all", "a", false, "Mount all containers")
- flags.StringVar(&mountOpts.Format, "format", "", "Change the output format to Go template")
- flags.BoolVarP(&mountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
- flags.BoolVar(&mountOpts.NoTruncate, "notruncate", false, "Do not truncate output")
+ mountFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerMountCommmand,
+ Parent: containerCmd,
+ })
+ containerMountFlags := containerMountCommmand.Flags()
+ mountFlags(containerMountFlags)
}
func mount(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/pause.go b/cmd/podman/containers/pause.go
index f3654b5c1..b932c4539 100644
--- a/cmd/podman/containers/pause.go
+++ b/cmd/podman/containers/pause.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -24,16 +25,38 @@ var (
podman pause -a`,
}
+ containerPauseCommand = &cobra.Command{
+ Use: pauseCommand.Use,
+ Short: pauseCommand.Short,
+ Long: pauseCommand.Long,
+ RunE: pauseCommand.RunE,
+ Example: `podman container pause mywebserver
+ podman container pause 860a4b23
+ podman container pause -a`,
+ }
+
pauseOpts = entities.PauseUnPauseOptions{}
)
+func pauseFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers")
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pauseCommand,
})
flags := pauseCommand.Flags()
- flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers")
+ pauseFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerPauseCommand,
+ Parent: containerCmd,
+ })
+ containerPauseFlags := containerPauseCommand.Flags()
+ pauseFlags(containerPauseFlags)
}
func pause(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/port.go b/cmd/podman/containers/port.go
new file mode 100644
index 000000000..2e3386aa9
--- /dev/null
+++ b/cmd/podman/containers/port.go
@@ -0,0 +1,123 @@
+package containers
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/parse"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ 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 [PORT]",
+ Short: "List port mappings or a specific mapping for the container",
+ Long: portDescription,
+ RunE: port,
+ Args: func(cmd *cobra.Command, args []string) error {
+ return parse.CheckAllLatestAndCIDFile(cmd, args, true, false)
+ },
+ Example: `podman port --all
+ podman port ctrID 80/tcp
+ podman port --latest 80`,
+ }
+)
+
+var (
+ portOpts entities.ContainerPortOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: portCommand,
+ })
+ flags := portCommand.Flags()
+ flags.BoolVarP(&portOpts.All, "all", "a", false, "Display port information for all containers")
+ flags.BoolVarP(&portOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ if registry.IsRemote() {
+ _ = flags.MarkHidden("latest")
+ }
+}
+
+func port(cmd *cobra.Command, args []string) error {
+ var (
+ container string
+ err error
+ userPort ocicni.PortMapping
+ )
+
+ if len(args) == 0 && !portOpts.Latest && !portOpts.All {
+ return errors.Errorf("you must supply a running container name or id")
+ }
+ if !portOpts.Latest && len(args) >= 1 {
+ container = args[0]
+ }
+ port := ""
+ if len(args) > 1 && !portOpts.Latest {
+ port = args[1]
+ }
+ if len(args) == 1 && portOpts.Latest {
+ port = args[0]
+ }
+ if len(port) > 0 {
+ fields := strings.Split(port, "/")
+ if len(fields) > 2 || len(fields) < 1 {
+ return errors.Errorf("port formats are port/protocol. '%s' is invalid", port)
+ }
+ if len(fields) == 1 {
+ fields = append(fields, "tcp")
+ }
+
+ portNum, err := strconv.Atoi(fields[0])
+ if err != nil {
+ return err
+ }
+ userPort = ocicni.PortMapping{
+ HostPort: 0,
+ ContainerPort: int32(portNum),
+ Protocol: fields[1],
+ HostIP: "",
+ }
+ }
+
+ reports, err := registry.ContainerEngine().ContainerPort(registry.GetContext(), container, portOpts)
+ if err != nil {
+ return err
+ }
+ var found bool
+ // Iterate mappings
+ for _, report := range reports {
+ for _, v := range report.Ports {
+ hostIP := v.HostIP
+ // Set host IP to 0.0.0.0 if blank
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ if portOpts.All {
+ fmt.Printf("%s\t", report.Id[:12])
+ }
+ // If not searching by port or port/proto, then dump what we see
+ if port == "" {
+ fmt.Printf("%d/%s -> %s:%d\n", v.ContainerPort, v.Protocol, hostIP, v.HostPort)
+ continue
+ }
+ if v.ContainerPort == userPort.ContainerPort {
+ fmt.Printf("%s:%d\n", hostIP, v.HostPort)
+ found = true
+ break
+ }
+ }
+ if !found && port != "" {
+ return errors.Errorf("failed to find published port %q", port)
+ }
+ }
+ return nil
+}
diff --git a/cmd/podman/containers/ps.go b/cmd/podman/containers/ps.go
index 57b81a609..c5696a158 100644
--- a/cmd/podman/containers/ps.go
+++ b/cmd/podman/containers/ps.go
@@ -1,7 +1,6 @@
package containers
import (
- "encoding/json"
"fmt"
"os"
"sort"
@@ -14,18 +13,20 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
psDescription = "Prints out information about the containers"
psCommand = &cobra.Command{
Use: "ps",
- Args: checkFlags,
+ Args: validate.NoArgs,
Short: "List containers",
Long: psDescription,
RunE: ps,
@@ -40,7 +41,7 @@ var (
}
filters []string
noTrunc bool
- defaultHeaders string = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES"
+ defaultHeaders = "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES"
)
func init() {
@@ -48,7 +49,10 @@ func init() {
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: psCommand,
})
- flags := psCommand.Flags()
+ listFlagSet(psCommand.Flags())
+}
+
+func listFlagSet(flags *pflag.FlagSet) {
flags.BoolVarP(&listOpts.All, "all", "a", false, "Show all the containers, default is only running containers")
flags.StringSliceVarP(&filters, "filter", "f", []string{}, "Filter output based on conditions given")
flags.StringVar(&listOpts.Format, "format", "", "Pretty-print containers to JSON or using a Go template")
@@ -60,9 +64,12 @@ func init() {
flags.BoolVarP(&listOpts.Pod, "pod", "p", false, "Print the ID and name of the pod the containers are associated with")
flags.BoolVarP(&listOpts.Quiet, "quiet", "q", false, "Print the numeric IDs of the containers only")
flags.BoolVarP(&listOpts.Size, "size", "s", false, "Display the total file sizes")
- flags.StringVar(&listOpts.Sort, "sort", "created", "Sort output by command, created, id, image, names, runningfor, size, or status")
flags.BoolVar(&listOpts.Sync, "sync", false, "Sync container state with OCI runtime")
flags.UintVarP(&listOpts.Watch, "watch", "w", 0, "Watch the ps output on an interval in seconds")
+
+ created := validate.ChoiceValue(&listOpts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status")
+ flags.Var(created, "sort", "Sort output by: "+created.Choices())
+
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
@@ -138,6 +145,9 @@ func getResponses() ([]entities.ListContainer, error) {
func ps(cmd *cobra.Command, args []string) error {
var responses []psReporter
+ if err := checkFlags(cmd, args); err != nil {
+ return err
+ }
for _, f := range filters {
split := strings.SplitN(f, "=", 2)
if len(split) == 1 {
@@ -166,14 +176,14 @@ func ps(cmd *cobra.Command, args []string) error {
responses = append(responses, psReporter{r})
}
- headers, row := createPsOut()
+ headers, format := createPsOut()
if cmd.Flag("format").Changed {
- row = listOpts.Format
- if !strings.HasPrefix(row, "\n") {
- row += "\n"
+ format = strings.TrimPrefix(listOpts.Format, "table ")
+ if !strings.HasPrefix(format, "\n") {
+ format += "\n"
}
}
- format := "{{range . }}" + row + "{{end}}"
+ format = "{{range . }}" + format + "{{end}}"
if !listOpts.Quiet && !cmd.Flag("format").Changed {
format = headers + format
}
@@ -224,7 +234,7 @@ func createPsOut() (string, string) {
}
headers := defaultHeaders
row += "{{.ID}}"
- row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.State}}\t{{.Ports}}\t{{.Names}}"
+ row += "\t{{.Image}}\t{{.Command}}\t{{.CreatedHuman}}\t{{.Status}}\t{{.Ports}}\t{{.Names}}"
if listOpts.Pod {
headers += "\tPOD ID\tPODNAME"
@@ -248,6 +258,14 @@ type psReporter struct {
entities.ListContainer
}
+// ImageID returns the ID of the container
+func (l psReporter) ImageID() string {
+ if !noTrunc {
+ return l.ListContainer.ImageID[0:12]
+ }
+ return l.ListContainer.ImageID
+}
+
// ID returns the ID of the container
func (l psReporter) ID() string {
if !noTrunc {
@@ -283,6 +301,11 @@ func (l psReporter) State() string {
return state
}
+// Status is a synonym for State()
+func (l psReporter) Status() string {
+ return l.State()
+}
+
// Command returns the container command in string format
func (l psReporter) Command() string {
return strings.Join(l.ListContainer.Command, " ")
@@ -332,7 +355,7 @@ func portsToString(ports []ocicni.PortMapping) string {
if len(ports) == 0 {
return ""
}
- //Sort the ports, so grouping continuous ports become easy.
+ // Sort the ports, so grouping continuous ports become easy.
sort.Slice(ports, func(i, j int) bool {
return comparePorts(ports[i], ports[j])
})
diff --git a/cmd/podman/containers/restart.go b/cmd/podman/containers/restart.go
index 68b6de4ca..1a9d7f6c7 100644
--- a/cmd/podman/containers/restart.go
+++ b/cmd/podman/containers/restart.go
@@ -11,12 +11,13 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
restartDescription = fmt.Sprintf(`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 %d seconds.`, defaultContainerConfig.Engine.StopTimeout)
+ A timeout before forcibly stopping can be set, but defaults to %d seconds.`, containerConfig.Engine.StopTimeout)
restartCommand = &cobra.Command{
Use: "restart [flags] CONTAINER [CONTAINER...]",
@@ -30,6 +31,16 @@ var (
podman restart --latest
podman restart ctrID1 ctrID2`,
}
+
+ containerRestartCommand = &cobra.Command{
+ Use: restartCommand.Use,
+ Short: restartCommand.Short,
+ Long: restartCommand.Long,
+ RunE: restartCommand.RunE,
+ Example: `podman container restart ctrID
+ podman container restart --latest
+ podman container restart ctrID1 ctrID2`,
+ }
)
var (
@@ -37,22 +48,35 @@ var (
restartTimeout uint
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: restartCommand,
- })
- flags := restartCommand.Flags()
+func restartFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers")
flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used")
- flags.UintVarP(&restartTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
+ flags.UintVarP(&restartTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
flags.SetNormalizeFunc(utils.AliasFlags)
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: restartCommand,
+ })
+ flags := restartCommand.Flags()
+ restartFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerRestartCommand,
+ Parent: containerCmd,
+ })
+
+ containerRestartFlags := containerRestartCommand.Flags()
+ restartFlags(containerRestartFlags)
+}
+
func restart(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
diff --git a/cmd/podman/containers/rm.go b/cmd/podman/containers/rm.go
index a22880d93..3021853a9 100644
--- a/cmd/podman/containers/rm.go
+++ b/cmd/podman/containers/rm.go
@@ -12,6 +12,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -31,18 +32,24 @@ var (
podman rm --force --all
podman rm -f c684f0d469f2`,
}
+
+ containerRmCommand = &cobra.Command{
+ Use: rmCommand.Use,
+ Short: rmCommand.Use,
+ Long: rmCommand.Long,
+ RunE: rmCommand.RunE,
+ Example: `podman container rm imageID
+ podman container rm mywebserver myflaskserver 860a4b23
+ podman container rm --force --all
+ podman container rm -f c684f0d469f2`,
+ }
)
var (
rmOptions = entities.RmOptions{}
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: rmCommand,
- })
- flags := rmCommand.Flags()
+func rmFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
@@ -56,7 +63,24 @@ func init() {
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("storage")
}
+}
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: rmCommand,
+ })
+ flags := rmCommand.Flags()
+ rmFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerRmCommand,
+ Parent: containerCmd,
+ })
+ containerRmFlags := containerRmCommand.Flags()
+ rmFlags(containerRmFlags)
}
func rm(cmd *cobra.Command, args []string) error {
@@ -71,11 +95,9 @@ func rm(cmd *cobra.Command, args []string) error {
}
responses, err := registry.ContainerEngine().ContainerRm(context.Background(), args, rmOptions)
if err != nil {
- // TODO exitcode is a global main variable to track exit codes.
- // we need this enabled
- //if len(c.InputArgs) < 2 {
- // exitCode = setExitCode(err)
- //}
+ if len(args) < 2 {
+ setExitCode(err)
+ }
return err
}
for _, r := range responses {
@@ -84,6 +106,7 @@ func rm(cmd *cobra.Command, args []string) error {
if errors.Cause(err) == define.ErrWillDeadlock {
logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve")
}
+ setExitCode(r.Err)
errs = append(errs, r.Err)
} else {
fmt.Println(r.Id)
@@ -91,3 +114,13 @@ func rm(cmd *cobra.Command, args []string) error {
}
return errs.PrintErrors()
}
+
+func setExitCode(err error) {
+ cause := errors.Cause(err)
+ switch cause {
+ case define.ErrNoSuchCtr:
+ registry.SetExitCode(1)
+ case define.ErrCtrStateInvalid:
+ registry.SetExitCode(2)
+ }
+}
diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go
index 91edb6bda..e3fe4cd0b 100644
--- a/cmd/podman/containers/run.go
+++ b/cmd/podman/containers/run.go
@@ -5,15 +5,16 @@ import (
"os"
"strings"
- "github.com/containers/common/pkg/config"
"github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -27,6 +28,16 @@ var (
podman run --network=host imageID dnf -y install java
podman run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`,
}
+
+ containerRunCommand = &cobra.Command{
+ Use: runCommand.Use,
+ Short: runCommand.Short,
+ Long: runCommand.Long,
+ RunE: runCommand.RunE,
+ Example: `podman container run imageID ls -alF /etc
+ podman container run --network=host imageID dnf -y install java
+ podman container run --volume /var/hostdir:/var/ctrdir -i -t fedora /bin/bash`,
+ }
)
var (
@@ -38,12 +49,7 @@ var (
runRmi bool
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: runCommand,
- })
- flags := runCommand.Flags()
+func runFlags(flags *pflag.FlagSet) {
flags.SetInterspersed(false)
flags.AddFlagSet(common.GetCreateFlags(&cliVals))
flags.AddFlagSet(common.GetNetFlags())
@@ -54,6 +60,23 @@ func init() {
_ = flags.MarkHidden("authfile")
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: runCommand,
+ })
+ flags := runCommand.Flags()
+ runFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerRunCommand,
+ Parent: containerCmd,
+ })
+
+ containerRunFlags := containerRunCommand.Flags()
+ runFlags(containerRunFlags)
+}
func run(cmd *cobra.Command, args []string) error {
var err error
@@ -67,31 +90,26 @@ func run(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "error checking authfile path %s", af)
}
}
- runOpts.Rm = cliVals.Rm
- if err := createInit(cmd); err != nil {
+ cidFile, err := openCidFile(cliVals.CIDFile)
+ if err != nil {
return err
}
- br, err := registry.ImageEngine().Exists(registry.GetContext(), args[0])
- if err != nil {
- return err
+ if cidFile != nil {
+ defer errorhandling.CloseQuiet(cidFile)
+ defer errorhandling.SyncQuiet(cidFile)
}
- pullPolicy, err := config.ValidatePullPolicy(cliVals.Pull)
- if err != nil {
+ runOpts.Rm = cliVals.Rm
+ if err := createInit(cmd); err != nil {
return err
}
- if !br.Value || pullPolicy == config.PullImageAlways {
- if pullPolicy == config.PullImageNever {
- return errors.New("unable to find a name and tag match for busybox in repotags: no such image")
- }
- _, pullErr := registry.ImageEngine().Pull(registry.GetContext(), args[0], entities.ImagePullOptions{
- Authfile: cliVals.Authfile,
- Quiet: cliVals.Quiet,
- })
- if pullErr != nil {
- return pullErr
+
+ if !cliVals.RootFS {
+ if err := pullImage(args[0]); err != nil {
+ return err
}
}
+
// If -i is not set, clear stdin
if !cliVals.Interactive {
runOpts.InputStream = nil
@@ -120,7 +138,7 @@ func run(cmd *cobra.Command, args []string) error {
}
runOpts.Detach = cliVals.Detach
runOpts.DetachKeys = cliVals.DetachKeys
- s := specgen.NewSpecGenerator(args[0])
+ s := specgen.NewSpecGenerator(args[0], cliVals.RootFS)
if err := common.FillOutSpecGen(s, &cliVals, args); err != nil {
return err
}
@@ -134,11 +152,19 @@ func run(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ if cidFile != nil {
+ _, err = cidFile.WriteString(report.Id)
+ if err != nil {
+ logrus.Error(err)
+ }
+ }
+
if cliVals.Detach {
fmt.Println(report.Id)
+ return nil
}
if runRmi {
- _, err := registry.ImageEngine().Delete(registry.GetContext(), []string{args[0]}, entities.ImageDeleteOptions{})
+ _, err := registry.ImageEngine().Remove(registry.GetContext(), []string{args[0]}, entities.ImageRemoveOptions{})
if err != nil {
logrus.Errorf("%s", errors.Wrapf(err, "failed removing image"))
}
diff --git a/cmd/podman/containers/start.go b/cmd/podman/containers/start.go
index 33e5a3094..381bf8e26 100644
--- a/cmd/podman/containers/start.go
+++ b/cmd/podman/containers/start.go
@@ -4,13 +4,13 @@ import (
"fmt"
"os"
- "github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -20,25 +20,29 @@ var (
Short: "Start one or more containers",
Long: startDescription,
RunE: start,
- Args: cobra.MinimumNArgs(1),
Example: `podman start --latest
podman start 860a4b231279 5421ab43b45
podman start --interactive --attach imageID`,
}
+
+ containerStartCommand = &cobra.Command{
+ Use: startCommand.Use,
+ Short: startCommand.Short,
+ Long: startCommand.Long,
+ RunE: startCommand.RunE,
+ Example: `podman container start --latest
+ podman container start 860a4b231279 5421ab43b45
+ podman container start --interactive --attach imageID`,
+ }
)
var (
startOptions entities.ContainerStartOptions
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode},
- Command: startCommand,
- })
- flags := startCommand.Flags()
+func startFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&startOptions.Attach, "attach", "a", false, "Attach container's STDOUT and STDERR")
- flags.StringVar(&startOptions.DetachKeys, "detach-keys", common.GetDefaultDetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`")
+ flags.StringVar(&startOptions.DetachKeys, "detach-keys", containerConfig.DetachKeys(), "Select the key sequence for detaching a container. Format is a single character `[a-Z]` or a comma separated sequence of `ctrl-<value>`, where `<value>` is one of: `a-z`, `@`, `^`, `[`, `\\`, `]`, `^` or `_`")
flags.BoolVarP(&startOptions.Interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
flags.BoolVarP(&startOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&startOptions.SigProxy, "sig-proxy", false, "Proxy received signals to the process (default true if attaching, false otherwise)")
@@ -46,11 +50,30 @@ func init() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("sig-proxy")
}
+}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: startCommand,
+ })
+ flags := startCommand.Flags()
+ startFlags(flags)
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerStartCommand,
+ Parent: containerCmd,
+ })
+
+ containerStartFlags := containerStartCommand.Flags()
+ startFlags(containerStartFlags)
}
func start(cmd *cobra.Command, args []string) error {
var errs utils.OutputErrors
+ if len(args) == 0 && !startOptions.Latest {
+ return errors.New("start requires at least one argument")
+ }
if len(args) > 1 && startOptions.Attach {
return errors.Errorf("you cannot start and attach multiple containers at once")
}
diff --git a/cmd/podman/containers/stop.go b/cmd/podman/containers/stop.go
index 1ee9186a7..4a451134a 100644
--- a/cmd/podman/containers/stop.go
+++ b/cmd/podman/containers/stop.go
@@ -9,12 +9,13 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
stopDescription = fmt.Sprintf(`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 %d seconds otherwise.`, defaultContainerConfig.Engine.StopTimeout)
+ A timeout to forcibly stop the container can also be set but defaults to %d seconds otherwise.`, containerConfig.Engine.StopTimeout)
stopCommand = &cobra.Command{
Use: "stop [flags] CONTAINER [CONTAINER...]",
Short: "Stop one or more containers",
@@ -27,6 +28,16 @@ var (
podman stop --latest
podman stop --time 2 mywebserver 6e534f14da9d`,
}
+
+ containerStopCommand = &cobra.Command{
+ Use: stopCommand.Use,
+ Short: stopCommand.Short,
+ Long: stopCommand.Long,
+ RunE: stopCommand.RunE,
+ Example: `podman container stop ctrID
+ podman container stop --latest
+ podman container stop --time 2 mywebserver 6e534f14da9d`,
+ }
)
var (
@@ -34,18 +45,14 @@ var (
stopTimeout uint
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: stopCommand,
- })
- flags := stopCommand.Flags()
+func stopFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers")
flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
- flags.UintVarP(&stopTimeout, "time", "t", defaultContainerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
- if registry.PodmanOptions.EngineMode == entities.ABIMode {
+ flags.UintVarP(&stopTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for stop before killing the container")
+
+ if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("ignore")
@@ -53,11 +60,29 @@ func init() {
flags.SetNormalizeFunc(utils.AliasFlags)
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: stopCommand,
+ })
+ flags := stopCommand.Flags()
+ stopFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerStopCommand,
+ Parent: containerCmd,
+ })
+
+ containerStopFlags := containerStopCommand.Flags()
+ stopFlags(containerStopFlags)
+}
+
func stop(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
- stopOptions.Timeout = defaultContainerConfig.Engine.StopTimeout
+ stopOptions.Timeout = containerConfig.Engine.StopTimeout
if cmd.Flag("time").Changed {
stopOptions.Timeout = stopTimeout
}
diff --git a/cmd/podman/containers/top.go b/cmd/podman/containers/top.go
index db5213863..732a08623 100644
--- a/cmd/podman/containers/top.go
+++ b/cmd/podman/containers/top.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/psgo"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -36,25 +37,46 @@ podman top --latest
podman top ctrID pid seccomp args %C
podman top ctrID -eo user,pid,comm`,
}
-)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: topCommand,
- })
+ containerTopCommand = &cobra.Command{
+ Use: topCommand.Use,
+ Short: topCommand.Short,
+ Long: topCommand.Long,
+ RunE: topCommand.RunE,
+ Example: `podman container top ctrID
+podman container top --latest
+podman container top ctrID pid seccomp args %C
+podman container top ctrID -eo user,pid,comm`,
+ }
+)
- flags := topCommand.Flags()
+func topFlags(flags *pflag.FlagSet) {
flags.SetInterspersed(false)
flags.BoolVar(&topOptions.ListDescriptors, "list-descriptors", false, "")
flags.BoolVarP(&topOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
-
_ = flags.MarkHidden("list-descriptors") // meant only for bash completion
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: topCommand,
+ })
+ flags := topCommand.Flags()
+ topFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerTopCommand,
+ Parent: containerCmd,
+ })
+ containerTopFlags := containerTopCommand.Flags()
+ topFlags(containerTopFlags)
+}
+
func top(cmd *cobra.Command, args []string) error {
if topOptions.ListDescriptors {
fmt.Println(strings.Join(psgo.ListDescriptors(), "\n"))
diff --git a/cmd/podman/containers/unmount.go b/cmd/podman/containers/unmount.go
index 3dbfc1eae..a4550abbd 100644
--- a/cmd/podman/containers/unmount.go
+++ b/cmd/podman/containers/unmount.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -30,21 +31,44 @@ var (
podman umount ctrID1 ctrID2 ctrID3
podman umount --all`,
}
+
+ containerUnmountCommand = &cobra.Command{
+ Use: umountCommand.Use,
+ Short: umountCommand.Short,
+ Long: umountCommand.Long,
+ RunE: umountCommand.RunE,
+ Example: `podman container umount ctrID
+ podman container umount ctrID1 ctrID2 ctrID3
+ podman container umount --all`,
+ }
)
var (
unmountOpts entities.ContainerUnmountOptions
)
+func umountFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers")
+ flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers")
+ flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: umountCommand,
})
flags := umountCommand.Flags()
- flags.BoolVarP(&unmountOpts.All, "all", "a", false, "Umount all of the currently mounted containers")
- flags.BoolVarP(&unmountOpts.Force, "force", "f", false, "Force the complete umount all of the currently mounted containers")
- flags.BoolVarP(&unmountOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
+ umountFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: containerUnmountCommand,
+ Parent: containerCmd,
+ })
+
+ containerUmountFlags := containerUnmountCommand.Flags()
+ umountFlags(containerUmountFlags)
}
func unmount(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/unpause.go b/cmd/podman/containers/unpause.go
index ef874b042..adf8d12ee 100644
--- a/cmd/podman/containers/unpause.go
+++ b/cmd/podman/containers/unpause.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -23,16 +24,37 @@ var (
podman unpause --all`,
}
unPauseOptions = entities.PauseUnPauseOptions{}
+
+ containerUnpauseCommand = &cobra.Command{
+ Use: unpauseCommand.Use,
+ Short: unpauseCommand.Short,
+ Long: unpauseCommand.Long,
+ RunE: unpauseCommand.RunE,
+ Example: `podman container unpause ctrID
+ podman container unpause --all`,
+ }
)
+func unpauseFlags(flags *pflag.FlagSet) {
+ flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers")
+}
+
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: unpauseCommand,
- Parent: containerCmd,
})
flags := unpauseCommand.Flags()
- flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers")
+ unpauseFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: unpauseCommand,
+ Parent: containerCmd,
+ })
+
+ unpauseCommandFlags := containerUnpauseCommand.Flags()
+ unpauseFlags(unpauseCommandFlags)
}
func unpause(cmd *cobra.Command, args []string) error {
diff --git a/cmd/podman/containers/wait.go b/cmd/podman/containers/wait.go
index 83c164e16..eac1e2956 100644
--- a/cmd/podman/containers/wait.go
+++ b/cmd/podman/containers/wait.go
@@ -7,10 +7,12 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
var (
@@ -21,11 +23,21 @@ var (
Short: "Block on one or more containers",
Long: waitDescription,
RunE: wait,
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Example: `podman wait --latest
podman wait --interval 5000 ctrID
podman wait ctrID1 ctrID2`,
}
+
+ containerWaitCommand = &cobra.Command{
+ Use: waitCommand.Use,
+ Short: waitCommand.Short,
+ Long: waitCommand.Long,
+ RunE: waitCommand.RunE,
+ Example: `podman container wait --latest
+ podman container wait --interval 5000 ctrID
+ podman container wait ctrID1 ctrID2`,
+ }
)
var (
@@ -33,22 +45,34 @@ var (
waitCondition string
)
-func init() {
- registry.Commands = append(registry.Commands, registry.CliCommand{
- Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
- Command: waitCommand,
- })
-
- flags := waitCommand.Flags()
+func waitFlags(flags *pflag.FlagSet) {
flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion")
flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on")
- if registry.PodmanOptions.EngineMode == entities.ABIMode {
+ if registry.IsRemote() {
// TODO: This is the same as V1. We could skip creating the flag altogether in V2...
_ = flags.MarkHidden("latest")
}
}
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: waitCommand,
+ })
+ flags := waitCommand.Flags()
+ waitFlags(flags)
+
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: containerWaitCommand,
+ Parent: containerCmd,
+ })
+
+ containerWaitFlags := containerWaitCommand.Flags()
+ waitFlags(containerWaitFlags)
+}
+
func wait(cmd *cobra.Command, args []string) error {
var (
err error
diff --git a/cmd/podman/diff.go b/cmd/podman/diff.go
index 8db76e8af..1ff2fce40 100644
--- a/cmd/podman/diff.go
+++ b/cmd/podman/diff.go
@@ -6,6 +6,7 @@ import (
"github.com/containers/libpod/cmd/podman/containers"
"github.com/containers/libpod/cmd/podman/images"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -17,7 +18,7 @@ var (
diffDescription = `Displays changes on a container or image's filesystem. The container or image will be compared to its parent layer.`
diffCmd = &cobra.Command{
Use: "diff [flags] {CONTAINER_ID | IMAGE_ID}",
- Args: registry.IdOrLatestArgs,
+ Args: validate.IdOrLatestArgs,
Short: "Display the changes of object's file system",
Long: diffDescription,
TraverseChildren: true,
@@ -46,10 +47,9 @@ func init() {
}
func diff(cmd *cobra.Command, args []string) error {
- if found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]); err != nil {
- return err
- } else if found.Value {
- return images.Diff(cmd, args, diffOpts)
+ // Latest implies looking for a container
+ if diffOpts.Latest {
+ return containers.Diff(cmd, args, diffOpts)
}
if found, err := registry.ContainerEngine().ContainerExists(registry.GetContext(), args[0]); err != nil {
@@ -57,5 +57,12 @@ func diff(cmd *cobra.Command, args []string) error {
} else if found.Value {
return containers.Diff(cmd, args, diffOpts)
}
+
+ if found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0]); err != nil {
+ return err
+ } else if found.Value {
+ return images.Diff(cmd, args, diffOpts)
+ }
+
return fmt.Errorf("%s not found on system", args[0])
}
diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go
new file mode 100644
index 000000000..b112e666a
--- /dev/null
+++ b/cmd/podman/generate/generate.go
@@ -0,0 +1,28 @@
+package pods
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/spf13/cobra"
+)
+
+var (
+ // Command: podman _generate_
+ generateCmd = &cobra.Command{
+ Use: "generate",
+ Short: "Generate structured data based on containers and pods.",
+ Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.",
+ TraverseChildren: true,
+ RunE: validate.SubCommandExists,
+ }
+ containerConfig = util.DefaultContainerConfig()
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode},
+ Command: generateCmd,
+ })
+}
diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go
new file mode 100644
index 000000000..55d770249
--- /dev/null
+++ b/cmd/podman/generate/systemd.go
@@ -0,0 +1,57 @@
+package pods
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ systemdTimeout uint
+ systemdOptions = entities.GenerateSystemdOptions{}
+ systemdDescription = `Generate systemd units for a pod or container.
+ The generated units can later be controlled via systemctl(1).`
+
+ systemdCmd = &cobra.Command{
+ Use: "systemd [flags] CTR|POD",
+ Short: "Generate systemd units.",
+ Long: systemdDescription,
+ RunE: systemd,
+ Args: cobra.MinimumNArgs(1),
+ Example: `podman generate systemd CTR
+ podman generate systemd --new --time 10 CTR
+ podman generate systemd --files --name POD`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: systemdCmd,
+ Parent: generateCmd,
+ })
+ flags := systemdCmd.Flags()
+ flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs")
+ flags.BoolVarP(&systemdOptions.Files, "files", "f", false, "Generate .service files instead of printing to stdout")
+ flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
+ flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy")
+ flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one")
+ flags.SetNormalizeFunc(utils.AliasFlags)
+}
+
+func systemd(cmd *cobra.Command, args []string) error {
+ if cmd.Flags().Changed("time") {
+ systemdOptions.StopTimeout = &systemdTimeout
+ }
+
+ report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(report.Output)
+ return nil
+}
diff --git a/cmd/podman/healthcheck/healthcheck.go b/cmd/podman/healthcheck/healthcheck.go
index 794a94615..ce90dba31 100644
--- a/cmd/podman/healthcheck/healthcheck.go
+++ b/cmd/podman/healthcheck/healthcheck.go
@@ -2,6 +2,7 @@ package healthcheck
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -13,7 +14,7 @@ var (
Short: "Manage Healthcheck",
Long: "Manage Healthcheck",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/images/diff.go b/cmd/podman/images/diff.go
index dd98dc4d6..7cfacfc6c 100644
--- a/cmd/podman/images/diff.go
+++ b/cmd/podman/images/diff.go
@@ -11,8 +11,8 @@ import (
var (
// podman container _inspect_
diffCmd = &cobra.Command{
- Use: "diff [flags] CONTAINER",
- Args: registry.IdOrLatestArgs,
+ Use: "diff [flags] IMAGE",
+ Args: cobra.ExactArgs(1),
Short: "Inspect changes on image's file systems",
Long: `Displays changes on a image's filesystem. The image will be compared to its parent layer.`,
RunE: diff,
@@ -32,16 +32,16 @@ func init() {
diffOpts = &entities.DiffOptions{}
flags := diffCmd.Flags()
flags.BoolVar(&diffOpts.Archive, "archive", true, "Save the diff as a tar archive")
- _ = flags.MarkHidden("archive")
+ _ = flags.MarkDeprecated("archive", "Provided for backwards compatibility, has no impact on output.")
flags.StringVar(&diffOpts.Format, "format", "", "Change the output format")
}
func diff(cmd *cobra.Command, args []string) error {
- if len(args) == 0 && !diffOpts.Latest {
- return errors.New("image must be specified: podman image diff [options [...]] ID-NAME")
+ if diffOpts.Latest {
+ return errors.New("image diff does not support --latest")
}
- results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], entities.DiffOptions{})
+ results, err := registry.ImageEngine().Diff(registry.GetContext(), args[0], *diffOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/images/exists.go b/cmd/podman/images/exists.go
index 0bb288b96..13191113f 100644
--- a/cmd/podman/images/exists.go
+++ b/cmd/podman/images/exists.go
@@ -1,8 +1,6 @@
package images
import (
- "os"
-
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
@@ -17,6 +15,7 @@ var (
RunE: exists,
Example: `podman image exists ID
podman image exists IMAGE && podman pull IMAGE`,
+ DisableFlagsInUseLine: true,
}
)
@@ -34,7 +33,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !found.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/images/history.go b/cmd/podman/images/history.go
index c92072bff..ce153aa46 100644
--- a/cmd/podman/images/history.go
+++ b/cmd/podman/images/history.go
@@ -13,7 +13,6 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/docker/go-units"
- jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -77,7 +76,6 @@ func history(cmd *cobra.Command, args []string) error {
layers[i].ImageHistoryLayer = l
layers[i].Created = l.Created.Format(time.RFC3339)
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(os.Stdout)
err = enc.Encode(layers)
}
@@ -91,22 +89,20 @@ func history(cmd *cobra.Command, args []string) error {
hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n"
row := "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- if len(opts.format) > 0 {
+ switch {
+ case len(opts.format) > 0:
hdr = ""
row = opts.format
if !strings.HasSuffix(opts.format, "\n") {
row += "\n"
}
- } else {
- switch {
- case opts.human:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- case opts.noTrunc:
- row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
- case opts.quiet:
- hdr = ""
- row = "{{.ID}}\n"
- }
+ case opts.quiet:
+ hdr = ""
+ row = "{{.ID}}\n"
+ case opts.human:
+ row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
+ case opts.noTrunc:
+ row = "{{.ID}}\t{{.Created}}\t{{.CreatedBy}}\t{{.Size}}\t{{.Comment}}\n"
}
format := hdr + "{{range . }}" + row + "{{end}}"
diff --git a/cmd/podman/images/image.go b/cmd/podman/images/image.go
index 37e46ab9e..790c16c05 100644
--- a/cmd/podman/images/image.go
+++ b/cmd/podman/images/image.go
@@ -2,18 +2,22 @@ package images
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _image_
imageCmd = &cobra.Command{
Use: "image",
Short: "Manage images",
Long: "Manage images",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/images/images.go b/cmd/podman/images/images.go
index fd3ede26a..96ef344bf 100644
--- a/cmd/podman/images/images.go
+++ b/cmd/podman/images/images.go
@@ -11,12 +11,13 @@ import (
var (
// podman _images_ Alias for podman image _list_
imagesCmd = &cobra.Command{
- Use: strings.Replace(listCmd.Use, "list", "images", 1),
- Args: listCmd.Args,
- Short: listCmd.Short,
- Long: listCmd.Long,
- RunE: listCmd.RunE,
- Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+ Use: strings.Replace(listCmd.Use, "list", "images", 1),
+ Args: listCmd.Args,
+ Short: listCmd.Short,
+ Long: listCmd.Long,
+ RunE: listCmd.RunE,
+ Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+ DisableFlagsInUseLine: true,
}
)
diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go
index 4482ceee5..8c727eb07 100644
--- a/cmd/podman/images/inspect.go
+++ b/cmd/podman/images/inspect.go
@@ -1,30 +1,22 @@
package images
import (
- "context"
- "encoding/json"
- "fmt"
- "os"
- "strings"
- "text/tabwriter"
- "text/template"
-
- "github.com/containers/buildah/pkg/formats"
- "github.com/containers/libpod/cmd/podman/common"
+ "github.com/containers/libpod/cmd/podman/inspect"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
// Command: podman image _inspect_
inspectCmd = &cobra.Command{
- Use: "inspect [flags] IMAGE",
- Short: "Display the configuration of an image",
- Long: `Displays the low-level information on an image identified by name or ID.`,
- RunE: inspect,
- Example: `podman image inspect alpine`,
+ Use: "inspect [flags] IMAGE",
+ Short: "Display the configuration of an image",
+ Long: `Displays the low-level information of an image identified by name or ID.`,
+ RunE: inspectExec,
+ Example: `podman inspect alpine
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine
+ podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`,
}
inspectOpts *entities.InspectOptions
)
@@ -35,74 +27,11 @@ func init() {
Command: inspectCmd,
Parent: imageCmd,
})
- inspectOpts = common.AddInspectFlagSet(inspectCmd)
-}
-
-func inspect(cmd *cobra.Command, args []string) error {
- latestContainer := inspectOpts.Latest
-
- if len(args) == 0 && !latestContainer {
- return errors.Errorf("container or image name must be specified: podman inspect [options [...]] name")
- }
-
- if len(args) > 0 && latestContainer {
- return errors.Errorf("you cannot provide additional arguments with --latest")
- }
-
- results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts)
- if err != nil {
- return err
- }
-
- if len(results.Images) > 0 {
- if inspectOpts.Format == "" {
- buf, err := json.MarshalIndent(results.Images, "", " ")
- if err != nil {
- return err
- }
- fmt.Println(string(buf))
-
- for id, e := range results.Errors {
- fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error())
- }
- return nil
- }
- row := inspectFormat(inspectOpts.Format)
- format := "{{range . }}" + row + "{{end}}"
- tmpl, err := template.New("inspect").Parse(format)
- if err != nil {
- return err
- }
-
- w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
- defer func() { _ = w.Flush() }()
- err = tmpl.Execute(w, results.Images)
- if err != nil {
- return err
- }
- }
-
- for id, e := range results.Errors {
- fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error())
- }
- return nil
-}
-
-func inspectFormat(row string) string {
- r := strings.NewReplacer("{{.Id}}", formats.IDString,
- ".Src", ".Source",
- ".Dst", ".Destination",
- ".ImageID", ".Image",
- )
- row = r.Replace(row)
-
- if !strings.HasSuffix(row, "\n") {
- row += "\n"
- }
- return row
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
+ flags := inspectCmd.Flags()
+ _ = flags.MarkHidden("latest") // Shared with container-inspect but not wanted here.
}
-func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error {
- inspectOpts = options
- return inspect(cmd, args)
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/images/list.go b/cmd/podman/images/list.go
index 366dfc4ba..83c039ed3 100644
--- a/cmd/podman/images/list.go
+++ b/cmd/podman/images/list.go
@@ -14,7 +14,6 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/docker/go-units"
- jsoniter "github.com/json-iterator/go"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -33,7 +32,7 @@ type listFlagType struct {
var (
// Command: podman image _list_
listCmd = &cobra.Command{
- Use: "list [flag] [IMAGE]",
+ Use: "list [FLAGS] [IMAGE]",
Aliases: []string{"ls"},
Args: cobra.MaximumNArgs(1),
Short: "List images in local storage",
@@ -42,6 +41,7 @@ var (
Example: `podman image list --format json
podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}"
podman image list --filter dangling=true`,
+ DisableFlagsInUseLine: true,
}
// Options to pull data
@@ -99,38 +99,54 @@ func images(cmd *cobra.Command, args []string) error {
return err
}
- imageS := summaries
- sort.Slice(imageS, sortFunc(listFlag.sort, imageS))
+ switch {
+ case listFlag.quiet:
+ return writeId(summaries)
+ case cmd.Flag("format").Changed && listFlag.format == "json":
+ return writeJSON(summaries)
+ default:
+ return writeTemplate(summaries)
+ }
+}
- if cmd.Flag("format").Changed && listFlag.format == "json" {
- return writeJSON(imageS)
- } else {
- return writeTemplate(imageS, err)
+func writeId(imageS []*entities.ImageSummary) error {
+ var ids = map[string]struct{}{}
+ for _, e := range imageS {
+ i := "sha256:" + e.ID
+ if !listFlag.noTrunc {
+ i = fmt.Sprintf("%12.12s", e.ID)
+ }
+ ids[i] = struct{}{}
}
+ for k := range ids {
+ fmt.Fprint(os.Stdout, k+"\n")
+ }
+ return nil
}
func writeJSON(imageS []*entities.ImageSummary) error {
type image struct {
entities.ImageSummary
- Created string
+ Created string
+ CreatedAt string
}
imgs := make([]image, 0, len(imageS))
for _, e := range imageS {
var h image
h.ImageSummary = *e
- h.Created = time.Unix(e.Created, 0).Format(time.RFC3339)
+ h.Created = units.HumanDuration(time.Since(e.Created)) + " ago"
+ h.CreatedAt = e.Created.Format(time.RFC3339Nano)
h.RepoTags = nil
imgs = append(imgs, h)
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
enc := json.NewEncoder(os.Stdout)
return enc.Encode(imgs)
}
-func writeTemplate(imageS []*entities.ImageSummary, err error) error {
+func writeTemplate(imageS []*entities.ImageSummary) error {
var (
hdr, row string
)
@@ -142,10 +158,11 @@ func writeTemplate(imageS []*entities.ImageSummary, err error) error {
h.Repository, h.Tag = tokenRepoTag(tag)
imgs = append(imgs, h)
}
- if e.IsReadOnly() {
- listFlag.readOnly = true
- }
+ listFlag.readOnly = e.IsReadOnly()
}
+
+ sort.Slice(imgs, sortFunc(listFlag.sort, imgs))
+
if len(listFlag.format) < 1 {
hdr, row = imageListFormat(listFlag)
} else {
@@ -175,37 +192,33 @@ func tokenRepoTag(tag string) (string, string) {
}
}
-func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool {
+func sortFunc(key string, data []imageReporter) func(i, j int) bool {
switch key {
case "id":
return func(i, j int) bool {
- return data[i].ID < data[j].ID
+ return data[i].ID() < data[j].ID()
}
case "repository":
return func(i, j int) bool {
- return data[i].RepoTags[0] < data[j].RepoTags[0]
+ return data[i].Repository < data[j].Repository
}
case "size":
return func(i, j int) bool {
- return data[i].Size < data[j].Size
+ return data[i].size() < data[j].size()
}
case "tag":
return func(i, j int) bool {
- return data[i].RepoTags[0] < data[j].RepoTags[0]
+ return data[i].Tag < data[j].Tag
}
default:
// case "created":
return func(i, j int) bool {
- return data[i].Created >= data[j].Created
+ return data[i].created().After(data[j].created())
}
}
}
func imageListFormat(flags listFlagType) (string, string) {
- if flags.quiet {
- return "", "{{.ID}}\n"
- }
-
// Defaults
hdr := "REPOSITORY\tTAG"
row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}"
@@ -255,11 +268,15 @@ func (i imageReporter) ID() string {
if !listFlag.noTrunc && len(i.ImageSummary.ID) >= 12 {
return i.ImageSummary.ID[0:12]
}
- return i.ImageSummary.ID
+ return "sha256:" + i.ImageSummary.ID
}
func (i imageReporter) Created() string {
- return units.HumanDuration(time.Since(time.Unix(i.ImageSummary.Created, 0))) + " ago"
+ return units.HumanDuration(time.Since(i.ImageSummary.Created)) + " ago"
+}
+
+func (i imageReporter) created() time.Time {
+ return i.ImageSummary.Created
}
func (i imageReporter) Size() string {
@@ -271,3 +288,19 @@ func (i imageReporter) Size() string {
func (i imageReporter) History() string {
return strings.Join(i.ImageSummary.History, ", ")
}
+
+func (i imageReporter) CreatedAt() string {
+ return i.ImageSummary.Created.String()
+}
+
+func (i imageReporter) CreatedSince() string {
+ return i.Created()
+}
+
+func (i imageReporter) CreatedTime() string {
+ return i.CreatedAt()
+}
+
+func (i imageReporter) size() int64 {
+ return i.ImageSummary.Size
+}
diff --git a/cmd/podman/images/load.go b/cmd/podman/images/load.go
index 23c657b59..f49f95002 100644
--- a/cmd/podman/images/load.go
+++ b/cmd/podman/images/load.go
@@ -6,6 +6,7 @@ import (
"io"
"io/ioutil"
"os"
+ "strings"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/libpod/cmd/podman/parse"
@@ -89,6 +90,6 @@ func load(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- fmt.Println("Loaded image: " + response.Name)
+ fmt.Println("Loaded image(s): " + strings.Join(response.Names, ","))
return nil
}
diff --git a/cmd/podman/images/prune.go b/cmd/podman/images/prune.go
index b90d889be..53a1966c1 100644
--- a/cmd/podman/images/prune.go
+++ b/cmd/podman/images/prune.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -18,7 +19,7 @@ var (
If an image is not being used by a container, it will be removed from the system.`
pruneCmd = &cobra.Command{
Use: "prune",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Remove unused images",
Long: pruneDescription,
RunE: prune,
diff --git a/cmd/podman/images/pull.go b/cmd/podman/images/pull.go
index fb107d00c..fead5f7ed 100644
--- a/cmd/podman/images/pull.go
+++ b/cmd/podman/images/pull.go
@@ -2,11 +2,13 @@ package images
import (
"fmt"
+ "os"
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
@@ -91,18 +93,22 @@ func pullFlags(flags *pflag.FlagSet) {
// imagePull is implement the command for pulling images.
func imagePull(cmd *cobra.Command, args []string) error {
- pullOptsAPI := pullOptions.ImagePullOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- pullOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
+ pullOptions.SkipTLSVerify = types.NewOptionalBool(!pullOptions.TLSVerifyCLI)
+ }
+ if pullOptions.Authfile != "" {
+ if _, err := os.Stat(pullOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pullOptions.Authfile)
+ }
}
// Let's do all the remaining Yoga in the API to prevent us from
// scattering logic across (too) many parts of the code.
- pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptsAPI)
+ pullReport, err := registry.ImageEngine().Pull(registry.GetContext(), args[0], pullOptions.ImagePullOptions)
if err != nil {
return err
}
diff --git a/cmd/podman/images/push.go b/cmd/podman/images/push.go
index f12a5ac86..0b3502d61 100644
--- a/cmd/podman/images/push.go
+++ b/cmd/podman/images/push.go
@@ -1,6 +1,8 @@
package images
import (
+ "os"
+
buildahcli "github.com/containers/buildah/pkg/cli"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
@@ -96,6 +98,7 @@ func imagePush(cmd *cobra.Command, args []string) error {
switch len(args) {
case 1:
source = args[0]
+ destination = args[0]
case 2:
source = args[0]
destination = args[1]
@@ -105,16 +108,21 @@ func imagePush(cmd *cobra.Command, args []string) error {
return errors.New("push requires at least one image name, or optionally a second to specify a different destination")
}
- pushOptsAPI := pushOptions.ImagePushOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- pushOptsAPI.TLSVerify = types.NewOptionalBool(pushOptions.TLSVerifyCLI)
+ pushOptions.SkipTLSVerify = types.NewOptionalBool(!pushOptions.TLSVerifyCLI)
+ }
+
+ if pushOptions.Authfile != "" {
+ if _, err := os.Stat(pushOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", pushOptions.Authfile)
+ }
}
// Let's do all the remaining Yoga in the API to prevent us from scattering
// logic across (too) many parts of the code.
- return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptsAPI)
+ return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions)
}
diff --git a/cmd/podman/images/rm.go b/cmd/podman/images/rm.go
index 135fda387..1cf5fa365 100644
--- a/cmd/podman/images/rm.go
+++ b/cmd/podman/images/rm.go
@@ -2,7 +2,6 @@ package images
import (
"fmt"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -23,7 +22,7 @@ var (
podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
}
- imageOpts = entities.ImageDeleteOptions{}
+ imageOpts = entities.ImageRemoveOptions{}
)
func init() {
@@ -40,32 +39,28 @@ func imageRemoveFlagSet(flags *pflag.FlagSet) {
flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
}
-func rm(cmd *cobra.Command, args []string) error {
+func rm(cmd *cobra.Command, args []string) error {
if len(args) < 1 && !imageOpts.All {
return errors.Errorf("image name or ID must be specified")
}
if len(args) > 0 && imageOpts.All {
return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
}
- report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts)
- if err != nil {
- switch {
- case report != nil && report.ImageNotFound != nil:
- fmt.Fprintln(os.Stderr, err.Error())
- registry.SetExitCode(2)
- case report != nil && report.ImageInUse != nil:
- fmt.Fprintln(os.Stderr, err.Error())
- default:
- return err
+
+ report, err := registry.ImageEngine().Remove(registry.GetContext(), args, imageOpts)
+ if report != nil {
+ for _, u := range report.Untagged {
+ fmt.Println("Untagged: " + u)
}
+ for _, d := range report.Deleted {
+ // Make sure an image was deleted (and not just untagged); else print it
+ if len(d) > 0 {
+ fmt.Println("Deleted: " + d)
+ }
+ }
+ registry.SetExitCode(report.ExitCode)
}
- for _, u := range report.Untagged {
- fmt.Println("Untagged: " + u)
- }
- for _, d := range report.Deleted {
- fmt.Println("Deleted: " + d)
- }
- return nil
+ return err
}
diff --git a/cmd/podman/images/search.go b/cmd/podman/images/search.go
index fdad94d45..a8abfb339 100644
--- a/cmd/podman/images/search.go
+++ b/cmd/podman/images/search.go
@@ -1,6 +1,7 @@
package images
import (
+ "os"
"reflect"
"strings"
@@ -47,14 +48,15 @@ var (
// Command: podman image search
imageSearchCmd = &cobra.Command{
- Use: searchCmd.Use,
- Short: searchCmd.Short,
- Long: searchCmd.Long,
- RunE: searchCmd.RunE,
- Args: searchCmd.Args,
+ Use: searchCmd.Use,
+ Short: searchCmd.Short,
+ Long: searchCmd.Long,
+ RunE: searchCmd.RunE,
+ Args: searchCmd.Args,
+ Annotations: searchCmd.Annotations,
Example: `podman image search --filter=is-official --limit 3 alpine
- podman image search registry.fedoraproject.org/ # only works with v2 registries
- podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
+ podman image search registry.fedoraproject.org/ # only works with v2 registries
+ podman image search --format "table {{.Index}} {{.Name}}" registry.fedoraproject.org/fedora`,
}
)
@@ -103,16 +105,21 @@ func imageSearch(cmd *cobra.Command, args []string) error {
return errors.Errorf("search requires exactly one argument")
}
- sarchOptsAPI := searchOptions.ImageSearchOptions
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
- sarchOptsAPI.TLSVerify = types.NewOptionalBool(pullOptions.TLSVerifyCLI)
+ searchOptions.SkipTLSVerify = types.NewOptionalBool(!searchOptions.TLSVerifyCLI)
}
- searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, sarchOptsAPI)
+ if searchOptions.Authfile != "" {
+ if _, err := os.Stat(searchOptions.Authfile); err != nil {
+ return errors.Wrapf(err, "error getting authfile %s", searchOptions.Authfile)
+ }
+ }
+
+ searchReport, err := registry.ImageEngine().Search(registry.GetContext(), searchTerm, searchOptions.ImageSearchOptions)
if err != nil {
return err
}
diff --git a/cmd/podman/images/tree.go b/cmd/podman/images/tree.go
new file mode 100644
index 000000000..5e82e9dea
--- /dev/null
+++ b/cmd/podman/images/tree.go
@@ -0,0 +1,40 @@
+package images
+
+import (
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ treeDescription = "Prints layer hierarchy of an image in a tree format"
+ treeCmd = &cobra.Command{
+ Use: "tree [flags] IMAGE",
+ Args: cobra.ExactArgs(1),
+ Short: treeDescription,
+ Long: treeDescription,
+ RunE: tree,
+ Example: "podman image tree alpine:latest",
+ }
+ treeOpts entities.ImageTreeOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: treeCmd,
+ Parent: imageCmd,
+ })
+ treeCmd.Flags().BoolVar(&treeOpts.WhatRequires, "whatrequires", false, "Show all child images and layers of the specified image")
+}
+
+func tree(_ *cobra.Command, args []string) error {
+ results, err := registry.ImageEngine().Tree(registry.Context(), args[0], treeOpts)
+ if err != nil {
+ return err
+ }
+ fmt.Println(results.Tree)
+ return nil
+}
diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go
index 0393303e8..a5fdaedc2 100644
--- a/cmd/podman/inspect.go
+++ b/cmd/podman/inspect.go
@@ -1,31 +1,26 @@
package main
import (
- "context"
- "fmt"
-
- "github.com/containers/libpod/cmd/podman/common"
- "github.com/containers/libpod/cmd/podman/containers"
- "github.com/containers/libpod/cmd/podman/images"
+ "github.com/containers/libpod/cmd/podman/inspect"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
-// Inspect is one of the outlier commands in that it operates on images/containers/...
-
var (
- inspectOpts *entities.InspectOptions
-
// Command: podman _inspect_ Object_ID
inspectCmd = &cobra.Command{
Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}",
- Args: cobra.ExactArgs(1),
Short: "Display the configuration of object denoted by ID",
Long: "Displays the low-level information on an object identified by name or ID",
TraverseChildren: true,
- RunE: inspect,
+ RunE: inspectExec,
+ Example: `podman inspect fedora
+ podman inspect --type image fedora
+ podman inspect CtrID ImgID
+ podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora`,
}
+ inspectOpts *entities.InspectOptions
)
func init() {
@@ -33,20 +28,9 @@ func init() {
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: inspectCmd,
})
- inspectOpts = common.AddInspectFlagSet(inspectCmd)
+ inspectOpts = inspect.AddInspectFlagSet(inspectCmd)
}
-func inspect(cmd *cobra.Command, args []string) error {
- if found, err := registry.ImageEngine().Exists(context.Background(), args[0]); err != nil {
- return err
- } else if found.Value {
- return images.Inspect(cmd, args, inspectOpts)
- }
-
- if found, err := registry.ContainerEngine().ContainerExists(context.Background(), args[0]); err != nil {
- return err
- } else if found.Value {
- return containers.Inspect(cmd, args, inspectOpts)
- }
- return fmt.Errorf("%s not found on system", args[0])
+func inspectExec(cmd *cobra.Command, args []string) error {
+ return inspect.Inspect(args, *inspectOpts)
}
diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go
new file mode 100644
index 000000000..223ce00f0
--- /dev/null
+++ b/cmd/podman/inspect/inspect.go
@@ -0,0 +1,159 @@
+package inspect
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+const (
+ // ImageType is the image type.
+ ImageType = "image"
+ // ContainerType is the container type.
+ ContainerType = "container"
+ // AllType can be of type ImageType or ContainerType.
+ AllType = "all"
+)
+
+// AddInspectFlagSet takes a command and adds the inspect flags and returns an
+// InspectOptions object.
+func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions {
+ opts := entities.InspectOptions{}
+
+ flags := cmd.Flags()
+ flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size")
+ flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json")
+ flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType))
+ flags.BoolVarP(&opts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of")
+
+ return &opts
+}
+
+// Inspect inspects the specified container/image names or IDs.
+func Inspect(namesOrIDs []string, options entities.InspectOptions) error {
+ inspector, err := newInspector(options)
+ if err != nil {
+ return err
+ }
+ return inspector.inspect(namesOrIDs)
+}
+
+// inspector allows for inspecting images and containers.
+type inspector struct {
+ containerEngine entities.ContainerEngine
+ imageEngine entities.ImageEngine
+ options entities.InspectOptions
+}
+
+// newInspector creates a new inspector based on the specified options.
+func newInspector(options entities.InspectOptions) (*inspector, error) {
+ switch options.Type {
+ case ImageType, ContainerType, AllType:
+ // Valid types.
+ default:
+ return nil, errors.Errorf("invalid type %q: must be %q, %q or %q", options.Type, ImageType, ContainerType, AllType)
+ }
+ if options.Type == ImageType {
+ if options.Latest {
+ return nil, errors.Errorf("latest is not supported for type %q", ImageType)
+ }
+ if options.Size {
+ return nil, errors.Errorf("size is not supported for type %q", ImageType)
+ }
+ }
+ return &inspector{
+ containerEngine: registry.ContainerEngine(),
+ imageEngine: registry.ImageEngine(),
+ options: options,
+ }, nil
+}
+
+// inspect inspects the specified container/image names or IDs.
+func (i *inspector) inspect(namesOrIDs []string) error {
+ // data - dumping place for inspection results.
+ var data []interface{}
+ ctx := context.Background()
+
+ if len(namesOrIDs) == 0 {
+ if !i.options.Latest {
+ return errors.New("no containers or images specified")
+ }
+ }
+
+ tmpType := i.options.Type
+ if i.options.Latest {
+ if len(namesOrIDs) > 0 {
+ return errors.New("latest and containers are not allowed")
+ }
+ tmpType = ContainerType // -l works with --type=all
+ }
+
+ // Inspect - note that AllType requires us to expensively query one-by-one.
+ switch tmpType {
+ case AllType:
+ all, err := i.inspectAll(ctx, namesOrIDs)
+ if err != nil {
+ return err
+ }
+ data = all
+ case ImageType:
+ imgData, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options)
+ if err != nil {
+ return err
+ }
+ for i := range imgData {
+ data = append(data, imgData[i])
+ }
+ case ContainerType:
+ ctrData, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options)
+ if err != nil {
+ return err
+ }
+ for i := range ctrData {
+ data = append(data, ctrData[i])
+ }
+ default:
+ return errors.Errorf("invalid type %q: must be %q, %q or %q", i.options.Type, ImageType, ContainerType, AllType)
+ }
+
+ var out formats.Writer
+ if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat
+ out = formats.JSONStructArray{Output: data}
+ } else {
+ out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)}
+ }
+ return out.Out()
+}
+
+func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, error) {
+ var data []interface{}
+ for _, name := range namesOrIDs {
+ imgData, err := i.imageEngine.Inspect(ctx, []string{name}, i.options)
+ if err == nil {
+ data = append(data, imgData[0])
+ continue
+ }
+ ctrData, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options)
+ if err != nil {
+ return nil, err
+ }
+ data = append(data, ctrData[0])
+ }
+ return data, nil
+}
+
+func inspectFormat(row string) string {
+ r := strings.NewReplacer(
+ "{{.Id}}", formats.IDString,
+ ".Src", ".Source",
+ ".Dst", ".Destination",
+ ".ImageID", ".Image",
+ )
+ return r.Replace(row)
+}
diff --git a/cmd/podman/login.go b/cmd/podman/login.go
new file mode 100644
index 000000000..1843a764d
--- /dev/null
+++ b/cmd/podman/login.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+ "context"
+ "os"
+
+ "github.com/containers/common/pkg/auth"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+type loginOptionsWrapper struct {
+ auth.LoginOptions
+ tlsVerify bool
+}
+
+var (
+ loginOptions = loginOptionsWrapper{}
+ loginCommand = &cobra.Command{
+ Use: "login [flags] REGISTRY",
+ Short: "Login to a container registry",
+ Long: "Login to a container registry on a specified server.",
+ RunE: login,
+ Args: cobra.ExactArgs(1),
+ Example: `podman login quay.io
+ podman login --username ... --password ... quay.io
+ podman login --authfile dir/auth.json quay.io`,
+ }
+)
+
+func init() {
+ // Note that the local and the remote client behave the same: both
+ // store credentials locally while the remote client will pass them
+ // over the wire to the endpoint.
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: loginCommand,
+ })
+ flags := loginCommand.Flags()
+
+ // Flags from the auth package.
+ flags.AddFlagSet(auth.GetLoginFlags(&loginOptions.LoginOptions))
+
+ // Podman flags.
+ flags.BoolVarP(&loginOptions.tlsVerify, "tls-verify", "", false, "Require HTTPS and verify certificates when contacting registries")
+ flags.BoolVarP(&loginOptions.GetLoginSet, "get-login", "", false, "Return the current login user for the registry")
+ loginOptions.Stdin = os.Stdin
+ loginOptions.Stdout = os.Stdout
+}
+
+// Implementation of podman-login.
+func login(cmd *cobra.Command, args []string) error {
+ var skipTLS types.OptionalBool
+
+ if cmd.Flags().Changed("tls-verify") {
+ skipTLS = types.NewOptionalBool(!loginOptions.tlsVerify)
+ }
+
+ sysCtx := types.SystemContext{
+ AuthFilePath: loginOptions.AuthFile,
+ DockerCertPath: loginOptions.CertDir,
+ DockerInsecureSkipTLSVerify: skipTLS,
+ }
+
+ return auth.Login(context.Background(), &sysCtx, &loginOptions.LoginOptions, args[0])
+}
diff --git a/cmd/podman/logout.go b/cmd/podman/logout.go
new file mode 100644
index 000000000..77bdc92b4
--- /dev/null
+++ b/cmd/podman/logout.go
@@ -0,0 +1,57 @@
+package main
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/auth"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ logoutOptions = auth.LogoutOptions{}
+ logoutCommand = &cobra.Command{
+ Use: "logout [flags] REGISTRY",
+ Short: "Logout of a container registry",
+ Long: "Remove the cached username and password for the registry.",
+ RunE: logout,
+ Args: cobra.MaximumNArgs(1),
+ Example: `podman logout quay.io
+ podman logout --authfile dir/auth.json quay.io
+ podman logout --all`,
+ }
+)
+
+func init() {
+ // Note that the local and the remote client behave the same: both
+ // store credentials locally while the remote client will pass them
+ // over the wire to the endpoint.
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: logoutCommand,
+ })
+ flags := logoutCommand.Flags()
+
+ // Flags from the auth package.
+ flags.AddFlagSet(auth.GetLogoutFlags(&logoutOptions))
+ logoutOptions.Stdin = os.Stdin
+ logoutOptions.Stdout = os.Stdout
+}
+
+// Implementation of podman-logout.
+func logout(cmd *cobra.Command, args []string) error {
+ sysCtx := types.SystemContext{AuthFilePath: logoutOptions.AuthFile}
+
+ registry := ""
+ if len(args) > 0 {
+ if logoutOptions.All {
+ return errors.New("--all takes no arguments")
+ }
+ registry = args[0]
+ }
+
+ return auth.Logout(&sysCtx, &logoutOptions, registry)
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index 5f7bfe3c9..481214a38 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -4,9 +4,10 @@ import (
"os"
_ "github.com/containers/libpod/cmd/podman/containers"
+ _ "github.com/containers/libpod/cmd/podman/generate"
_ "github.com/containers/libpod/cmd/podman/healthcheck"
_ "github.com/containers/libpod/cmd/podman/images"
- _ "github.com/containers/libpod/cmd/podman/networks"
+ _ "github.com/containers/libpod/cmd/podman/manifest"
_ "github.com/containers/libpod/cmd/podman/pods"
"github.com/containers/libpod/cmd/podman/registry"
_ "github.com/containers/libpod/cmd/podman/system"
@@ -14,12 +15,6 @@ import (
"github.com/containers/storage/pkg/reexec"
)
-func init() {
- // This is the bootstrap configuration, if user gives
- // CLI flags parts of this configuration may be overwritten
- registry.PodmanOptions = registry.NewPodmanConfig()
-}
-
func main() {
if reexec.Init() {
// We were invoked with a different argv[0] indicating that we
@@ -27,9 +22,10 @@ func main() {
return
}
+ cfg := registry.PodmanConfig()
for _, c := range registry.Commands {
for _, m := range c.Mode {
- if registry.PodmanOptions.EngineMode == m {
+ if cfg.EngineMode == m {
parent := rootCmd
if c.Parent != nil {
parent = c.Parent
diff --git a/cmd/podman/manifest/add.go b/cmd/podman/manifest/add.go
new file mode 100644
index 000000000..38f832fad
--- /dev/null
+++ b/cmd/podman/manifest/add.go
@@ -0,0 +1,50 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestAddOpts = entities.ManifestAddOptions{}
+ addCmd = &cobra.Command{
+ Use: "add [flags] LIST LIST",
+ Short: "Add images to a manifest list or image index",
+ Long: "Adds an image to a manifest list or image index.",
+ RunE: add,
+ Example: `podman manifest add mylist:v1.11 image:v1.11-amd64
+ podman manifest add mylist:v1.11 transport:imageName`,
+ Args: cobra.ExactArgs(2),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: addCmd,
+ Parent: manifestCmd,
+ })
+ flags := addCmd.Flags()
+ flags.BoolVar(&manifestAddOpts.All, "all", false, "add all of the list's images if the image is a list")
+ flags.StringSliceVar(&manifestAddOpts.Annotation, "annotation", nil, "set an `annotation` for the specified image")
+ flags.StringVar(&manifestAddOpts.Arch, "arch", "", "override the `architecture` of the specified image")
+ flags.StringSliceVar(&manifestAddOpts.Features, "features", nil, "override the `features` of the specified image")
+ flags.StringVar(&manifestAddOpts.OS, "os", "", "override the `OS` of the specified image")
+ flags.StringVar(&manifestAddOpts.OSVersion, "os-version", "", "override the OS `version` of the specified image")
+ flags.StringVar(&manifestAddOpts.Variant, "variant", "", "override the `Variant` of the specified image")
+}
+
+func add(cmd *cobra.Command, args []string) error {
+ manifestAddOpts.Images = []string{args[1], args[0]}
+ listID, err := registry.ImageEngine().ManifestAdd(context.Background(), manifestAddOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error adding to manifest list %s", args[0])
+ }
+ fmt.Printf("%s\n", listID)
+ return nil
+}
diff --git a/cmd/podman/manifest/create.go b/cmd/podman/manifest/create.go
new file mode 100644
index 000000000..9c0097b90
--- /dev/null
+++ b/cmd/podman/manifest/create.go
@@ -0,0 +1,44 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestCreateOpts = entities.ManifestCreateOptions{}
+ createCmd = &cobra.Command{
+ Use: "create [flags] LIST [IMAGE]",
+ Short: "Create manifest list or image index",
+ Long: "Creates manifest lists or image indexes.",
+ RunE: create,
+ Example: `podman manifest create mylist:v1.11
+ podman manifest create mylist:v1.11 arch-specific-image-to-add
+ podman manifest create --all mylist:v1.11 transport:tagged-image-to-add`,
+ Args: cobra.MinimumNArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: createCmd,
+ Parent: manifestCmd,
+ })
+ flags := createCmd.Flags()
+ flags.BoolVar(&manifestCreateOpts.All, "all", false, "add all of the lists' images if the images to add are lists")
+}
+
+func create(cmd *cobra.Command, args []string) error {
+ imageID, err := registry.ImageEngine().ManifestCreate(context.Background(), args[:1], args[1:], manifestCreateOpts)
+ if err != nil {
+ return errors.Wrapf(err, "error creating manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", imageID)
+ return nil
+}
diff --git a/cmd/podman/manifest/inspect.go b/cmd/podman/manifest/inspect.go
new file mode 100644
index 000000000..5112aa5b2
--- /dev/null
+++ b/cmd/podman/manifest/inspect.go
@@ -0,0 +1,39 @@
+package manifest
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ inspectCmd = &cobra.Command{
+ Use: "inspect [flags] IMAGE",
+ Short: "Display the contents of a manifest list or image index",
+ Long: "Display the contents of a manifest list or image index.",
+ RunE: inspect,
+ Example: "podman manifest inspect localhost/list",
+ Args: cobra.ExactArgs(1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: inspectCmd,
+ Parent: manifestCmd,
+ })
+}
+
+func inspect(cmd *cobra.Command, args []string) error {
+ buf, err := registry.ImageEngine().ManifestInspect(context.Background(), args[0])
+ if err != nil {
+ return errors.Wrapf(err, "error inspect manifest %s", args[0])
+ }
+ fmt.Printf("%s\n", buf)
+ return nil
+}
diff --git a/cmd/podman/manifest/manifest.go b/cmd/podman/manifest/manifest.go
new file mode 100644
index 000000000..b78879b34
--- /dev/null
+++ b/cmd/podman/manifest/manifest.go
@@ -0,0 +1,28 @@
+package manifest
+
+import (
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ manifestDescription = "Creates, modifies, and pushes manifest lists and image indexes."
+ manifestCmd = &cobra.Command{
+ Use: "manifest",
+ Short: "Manipulate manifest lists and image indexes",
+ Long: manifestDescription,
+ TraverseChildren: true,
+ RunE: validate.SubCommandExists,
+ Example: `podman manifest create localhost/list
+ podman manifest inspect localhost/list`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: manifestCmd,
+ })
+}
diff --git a/cmd/podman/networks/network.go b/cmd/podman/networks/network.go
index 3cee86bcc..e2a928312 100644
--- a/cmd/podman/networks/network.go
+++ b/cmd/podman/networks/network.go
@@ -2,6 +2,7 @@ package images
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
@@ -13,10 +14,13 @@ var (
Short: "Manage networks",
Long: "Manage networks",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
+// TODO add the following to main.go to get networks back onto the
+// command list.
+// _ "github.com/containers/libpod/cmd/podman/networks"
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
diff --git a/cmd/podman/parse/net.go b/cmd/podman/parse/net.go
index 03cda268c..f93c4ab1e 100644
--- a/cmd/podman/parse/net.go
+++ b/cmd/podman/parse/net.go
@@ -1,4 +1,4 @@
-//nolint
+// nolint
// most of these validate and parse functions have been taken from projectatomic/docker
// and modified for cri-o
package parse
@@ -46,7 +46,7 @@ var (
// validateExtraHost validates that the specified string is a valid extrahost and returns it.
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
// for add-host flag
-func ValidateExtraHost(val string) (string, error) { //nolint
+func ValidateExtraHost(val string) (string, error) { // nolint
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
arr := strings.SplitN(val, ":", 2)
if len(arr) != 2 || len(arr[0]) == 0 {
diff --git a/cmd/podman/parse/net_test.go b/cmd/podman/parse/net_test.go
index a6ddc2be9..51c8509df 100644
--- a/cmd/podman/parse/net_test.go
+++ b/cmd/podman/parse/net_test.go
@@ -1,4 +1,4 @@
-//nolint
+// nolint
// most of these validate and parse functions have been taken from projectatomic/docker
// and modified for cri-o
package parse
@@ -41,7 +41,7 @@ func TestValidateExtraHost(t *testing.T) {
want string
wantErr bool
}{
- //2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ // 2001:0db8:85a3:0000:0000:8a2e:0370:7334
{name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false},
{name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true},
{name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true},
diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go
index 63dab4707..85b96d37b 100644
--- a/cmd/podman/pods/create.go
+++ b/cmd/podman/pods/create.go
@@ -9,7 +9,7 @@ import (
"github.com/containers/libpod/cmd/podman/common"
"github.com/containers/libpod/cmd/podman/parse"
"github.com/containers/libpod/cmd/podman/registry"
- "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/specgen"
@@ -25,7 +25,7 @@ var (
createCommand = &cobra.Command{
Use: "create",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Create a new empty pod",
Long: podCreateDescription,
RunE: create,
@@ -50,8 +50,8 @@ func init() {
flags.AddFlagSet(common.GetNetFlags())
flags.StringVar(&createOptions.CGroupParent, "cgroup-parent", "", "Set parent cgroup for the pod")
flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with")
- flags.StringVar(&createOptions.InfraImage, "infra-image", define.DefaultInfraImage, "The image of the infra container to associate with the pod")
- flags.StringVar(&createOptions.InfraCommand, "infra-command", define.DefaultInfraCommand, "The command to run on the infra container when the pod is started")
+ flags.StringVar(&createOptions.InfraImage, "infra-image", containerConfig.Engine.InfraImage, "The image of the infra container to associate with the pod")
+ flags.StringVar(&createOptions.InfraCommand, "infra-command", containerConfig.Engine.InfraCommand, "The command to run on the infra container when the pod is started")
flags.StringSliceVar(&labelFile, "label-file", []string{}, "Read in a line delimited file of labels")
flags.StringSliceVarP(&labels, "label", "l", []string{}, "Set metadata on pod (default [])")
flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod")
@@ -70,10 +70,24 @@ func create(cmd *cobra.Command, args []string) error {
return errors.Wrapf(err, "unable to process labels")
}
- if !createOptions.Infra && cmd.Flag("share").Changed && share != "none" && share != "" {
- return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container")
+ if !createOptions.Infra {
+ if cmd.Flag("infra-command").Changed {
+ return errors.New("cannot set infra-command without an infra container")
+ }
+ createOptions.InfraCommand = ""
+ if cmd.Flag("infra-image").Changed {
+ return errors.New("cannot set infra-image without an infra container")
+ }
+ createOptions.InfraImage = ""
+
+ if cmd.Flag("share").Changed && share != "none" && share != "" {
+ return fmt.Errorf("cannot set share(%s) namespaces without an infra container", cmd.Flag("share").Value)
+ }
+ createOptions.Share = nil
+ } else {
+ createOptions.Share = strings.Split(share, ",")
}
- createOptions.Share = strings.Split(share, ",")
+
if cmd.Flag("pod-id-file").Changed {
podIdFile, err = util.OpenExclusiveFile(podIDFile)
if err != nil && os.IsExist(err) {
@@ -103,7 +117,7 @@ func create(cmd *cobra.Command, args []string) error {
case "slip4netns":
n.NSMode = specgen.Slirp
default:
- if strings.HasPrefix(netInput, "container:") { //nolint
+ if strings.HasPrefix(netInput, "container:") { // nolint
split := strings.Split(netInput, ":")
if len(split) != 2 {
return errors.Errorf("invalid network paramater: %q", netInput)
@@ -123,21 +137,6 @@ func create(cmd *cobra.Command, args []string) error {
}
}
- if !createOptions.Infra {
- if cmd.Flag("infra-command").Changed {
- return errors.New("cannot set infra-command without an infra container")
- }
- createOptions.InfraCommand = ""
- if cmd.Flag("infra-image").Changed {
- return errors.New("cannot set infra-image without an infra container")
- }
- createOptions.InfraImage = ""
- if cmd.Flag("share").Changed {
- return errors.New("cannot set share namespaces without an infra container")
- }
- createOptions.Share = nil
- }
-
response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions)
if err != nil {
return err
diff --git a/cmd/podman/pods/exists.go b/cmd/podman/pods/exists.go
index ad0e28b90..cf3e3eae5 100644
--- a/cmd/podman/pods/exists.go
+++ b/cmd/podman/pods/exists.go
@@ -2,7 +2,6 @@ package pods
import (
"context"
- "os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
@@ -20,6 +19,7 @@ var (
Args: cobra.ExactArgs(1),
Example: `podman pod exists podID
podman pod exists mypod || podman pod create --name mypod`,
+ DisableFlagsInUseLine: true,
}
)
@@ -37,7 +37,7 @@ func exists(cmd *cobra.Command, args []string) error {
return err
}
if !response.Value {
- os.Exit(1)
+ registry.SetExitCode(1)
}
return nil
}
diff --git a/cmd/podman/pods/inspect.go b/cmd/podman/pods/inspect.go
index 901ae50b2..1e333247b 100644
--- a/cmd/podman/pods/inspect.go
+++ b/cmd/podman/pods/inspect.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
- jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -55,7 +54,7 @@ func inspect(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- b, err := jsoniter.MarshalIndent(responses, "", " ")
+ b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err
}
diff --git a/cmd/podman/pods/pod.go b/cmd/podman/pods/pod.go
index 1cac50e40..edca08202 100644
--- a/cmd/podman/pods/pod.go
+++ b/cmd/podman/pods/pod.go
@@ -2,19 +2,25 @@ package pods
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _pod_
podCmd = &cobra.Command{
Use: "pod",
Short: "Manage pods",
Long: "Manage pods",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
+ containerConfig = util.DefaultContainerConfig()
)
func init() {
diff --git a/cmd/podman/pods/prune.go b/cmd/podman/pods/prune.go
new file mode 100644
index 000000000..091e3375b
--- /dev/null
+++ b/cmd/podman/pods/prune.go
@@ -0,0 +1,75 @@
+package pods
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ pruneOptions = entities.PodPruneOptions{}
+)
+
+var (
+ pruneDescription = fmt.Sprintf(`podman pod prune Removes all exited pods`)
+
+ pruneCommand = &cobra.Command{
+ Use: "prune [flags]",
+ Short: "Remove all stopped pods and their containers",
+ Long: pruneDescription,
+ RunE: prune,
+ Example: `podman pod prune`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: pruneCommand,
+ Parent: podCmd,
+ })
+ flags := pruneCommand.Flags()
+ flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation. The default is false")
+}
+
+func prune(cmd *cobra.Command, args []string) error {
+ var (
+ errs utils.OutputErrors
+ )
+ if len(args) > 0 {
+ return errors.Errorf("`%s` takes no arguments", cmd.CommandPath())
+ }
+ if !pruneOptions.Force {
+ reader := bufio.NewReader(os.Stdin)
+ fmt.Println("WARNING! This will remove all stopped/exited pods..")
+ fmt.Print("Are you sure you want to continue? [y/N] ")
+ answer, err := reader.ReadString('\n')
+ if err != nil {
+ return errors.Wrapf(err, "error reading input")
+ }
+ if strings.ToLower(answer)[0] != 'y' {
+ return nil
+ }
+ }
+ responses, err := registry.ContainerEngine().PodPrune(context.Background(), pruneOptions)
+
+ if err != nil {
+ return err
+ }
+ for _, r := range responses {
+ if r.Err == nil {
+ fmt.Println(r.Id)
+ } else {
+ errs = append(errs, r.Err)
+ }
+ }
+ return errs.PrintErrors()
+}
diff --git a/cmd/podman/pods/ps.go b/cmd/podman/pods/ps.go
index 8cb7b6266..b97dfeb66 100644
--- a/cmd/podman/pods/ps.go
+++ b/cmd/podman/pods/ps.go
@@ -2,19 +2,19 @@ package pods
import (
"context"
- "encoding/json"
"fmt"
"io"
"os"
+ "sort"
"strings"
"text/tabwriter"
"text/template"
"time"
- "github.com/docker/go-units"
-
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -29,12 +29,13 @@ var (
Short: "list pods",
Long: psDescription,
RunE: pods,
+ Args: validate.NoArgs,
}
)
var (
- defaultHeaders string = "POD ID\tNAME\tSTATUS\tCREATED"
- inputFilters string
+ defaultHeaders = "POD ID\tNAME\tSTATUS\tCREATED"
+ inputFilters []string
noTrunc bool
psInput entities.PodPSOptions
)
@@ -50,7 +51,7 @@ func init() {
flags.BoolVar(&psInput.CtrIds, "ctr-ids", false, "Display the container UUIDs. If no-trunc is not set they will be truncated")
flags.BoolVar(&psInput.CtrStatus, "ctr-status", false, "Display the container status")
// TODO should we make this a [] ?
- flags.StringVarP(&inputFilters, "filter", "f", "", "Filter output based on conditions given")
+ flags.StringSliceVarP(&inputFilters, "filter", "f", []string{}, "Filter output based on conditions given")
flags.StringVar(&psInput.Format, "format", "", "Pretty-print pods to JSON or using a Go template")
flags.BoolVarP(&psInput.Latest, "latest", "l", false, "Act on the latest pod podman is aware of")
flags.BoolVar(&psInput.Namespace, "namespace", false, "Display namespace information of the pod")
@@ -69,8 +70,13 @@ func pods(cmd *cobra.Command, args []string) error {
row string
lpr []ListPodReporter
)
+
+ if psInput.Quiet && len(psInput.Format) > 0 {
+ return errors.New("quiet and format cannot be used together")
+ }
if cmd.Flag("filter").Changed {
- for _, f := range strings.Split(inputFilters, ",") {
+ psInput.Filters = make(map[string][]string)
+ for _, f := range inputFilters {
split := strings.Split(f, "=")
if len(split) < 2 {
return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
@@ -83,6 +89,10 @@ func pods(cmd *cobra.Command, args []string) error {
return err
}
+ if err := sortPodPsOutput(psInput.Sort, responses); err != nil {
+ return err
+ }
+
if psInput.Format == "json" {
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
@@ -97,11 +107,7 @@ func pods(cmd *cobra.Command, args []string) error {
}
headers, row := createPodPsOut()
if psInput.Quiet {
- if noTrunc {
- row = "{{.Id}}\n"
- } else {
- row = "{{slice .Id 0 12}}\n"
- }
+ row = "{{.Id}}\n"
}
if cmd.Flag("format").Changed {
row = psInput.Format
@@ -132,11 +138,7 @@ func pods(cmd *cobra.Command, args []string) error {
func createPodPsOut() (string, string) {
var row string
headers := defaultHeaders
- if noTrunc {
- row += "{{.Id}}"
- } else {
- row += "{{slice .Id 0 12}}"
- }
+ row += "{{.Id}}"
row += "\t{{.Name}}\t{{.Status}}\t{{.Created}}"
@@ -162,11 +164,7 @@ func createPodPsOut() (string, string) {
}
headers += "\tINFRA ID\n"
- if noTrunc {
- row += "\t{{.InfraId}}\n"
- } else {
- row += "\t{{slice .InfraId 0 12}}\n"
- }
+ row += "\t{{.InfraId}}\n"
return headers, row
}
@@ -186,6 +184,19 @@ func (l ListPodReporter) NumberOfContainers() int {
return len(l.Containers)
}
+// ID is a wrapper to Id for compat, typos
+func (l ListPodReporter) ID() string {
+ return l.Id()
+}
+
+// Id returns the Pod id
+func (l ListPodReporter) Id() string {
+ if noTrunc {
+ return l.ListPodsReport.Id
+ }
+ return l.ListPodsReport.Id[0:12]
+}
+
// Added for backwards compatibility with podmanv1
func (l ListPodReporter) InfraID() string {
return l.InfraId()
@@ -194,6 +205,9 @@ func (l ListPodReporter) InfraID() string {
// InfraId returns the infra container id for the pod
// depending on trunc
func (l ListPodReporter) InfraId() string {
+ if len(l.ListPodsReport.InfraId) == 0 {
+ return ""
+ }
if noTrunc {
return l.ListPodsReport.InfraId
}
@@ -227,3 +241,52 @@ func (l ListPodReporter) ContainerStatuses() string {
}
return strings.Join(statuses, ",")
}
+
+func sortPodPsOutput(sortBy string, lprs []*entities.ListPodsReport) error {
+ switch sortBy {
+ case "created":
+ sort.Sort(podPsSortedCreated{lprs})
+ case "id":
+ sort.Sort(podPsSortedId{lprs})
+ case "name":
+ sort.Sort(podPsSortedName{lprs})
+ case "number":
+ sort.Sort(podPsSortedNumber{lprs})
+ case "status":
+ sort.Sort(podPsSortedStatus{lprs})
+ default:
+ return errors.Errorf("invalid option for --sort, options are: id, names, or number")
+ }
+ return nil
+}
+
+type lprSort []*entities.ListPodsReport
+
+func (a lprSort) Len() int { return len(a) }
+func (a lprSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type podPsSortedCreated struct{ lprSort }
+
+func (a podPsSortedCreated) Less(i, j int) bool {
+ return a.lprSort[i].Created.After(a.lprSort[j].Created)
+}
+
+type podPsSortedId struct{ lprSort }
+
+func (a podPsSortedId) Less(i, j int) bool { return a.lprSort[i].Id < a.lprSort[j].Id }
+
+type podPsSortedNumber struct{ lprSort }
+
+func (a podPsSortedNumber) Less(i, j int) bool {
+ return len(a.lprSort[i].Containers) < len(a.lprSort[j].Containers)
+}
+
+type podPsSortedName struct{ lprSort }
+
+func (a podPsSortedName) Less(i, j int) bool { return a.lprSort[i].Name < a.lprSort[j].Name }
+
+type podPsSortedStatus struct{ lprSort }
+
+func (a podPsSortedStatus) Less(i, j int) bool {
+ return a.lprSort[i].Status < a.lprSort[j].Status
+}
diff --git a/cmd/podman/pods/rm.go b/cmd/podman/pods/rm.go
index ea3a6476a..4b9882f8a 100644
--- a/cmd/podman/pods/rm.go
+++ b/cmd/podman/pods/rm.go
@@ -41,10 +41,10 @@ func init() {
})
flags := rmCommand.Flags()
- flags.BoolVarP(&rmOptions.All, "all", "a", false, "Restart all running pods")
+ flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all running pods")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running pod by first stopping all containers, then removing all containers in the pod. The default is false")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing")
- flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Restart the latest pod podman is aware of")
+ flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Remove the latest pod podman is aware of")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("ignore")
diff --git a/cmd/podman/pods/stats.go b/cmd/podman/pods/stats.go
new file mode 100644
index 000000000..7c3597d9a
--- /dev/null
+++ b/cmd/podman/pods/stats.go
@@ -0,0 +1,189 @@
+package pods
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "reflect"
+ "strings"
+ "text/tabwriter"
+ "text/template"
+ "time"
+
+ "github.com/buger/goterm"
+ "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util/camelcase"
+ "github.com/spf13/cobra"
+)
+
+type podStatsOptionsWrapper struct {
+ entities.PodStatsOptions
+
+ // Format - pretty-print to JSON or a go template.
+ Format string
+ // NoReset - do not reset the screen when streaming.
+ NoReset bool
+ // NoStream - do not stream stats but write them once.
+ NoStream bool
+}
+
+var (
+ statsOptions = podStatsOptionsWrapper{}
+ statsDescription = `Display the containers' resource-usage statistics of one or more running pod`
+ // Command: podman pod _pod_
+ statsCmd = &cobra.Command{
+ Use: "stats [flags] [POD...]",
+ Short: "Display resource-usage statistics of pods",
+ Long: statsDescription,
+ RunE: stats,
+ Example: `podman pod stats
+ podman pod stats a69b23034235 named-pod
+ podman pod stats --latest
+ podman pod stats --all`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: statsCmd,
+ Parent: podCmd,
+ })
+
+ flags := statsCmd.Flags()
+ flags.BoolVarP(&statsOptions.All, "all", "a", false, "Provide stats for all pods")
+ flags.StringVar(&statsOptions.Format, "format", "", "Pretty-print container statistics to JSON or using a Go template")
+ flags.BoolVarP(&statsOptions.Latest, "latest", "l", false, "Provide stats on the latest pod Podman is aware of")
+ flags.BoolVar(&statsOptions.NoReset, "no-reset", false, "Disable resetting the screen when streaming")
+ flags.BoolVar(&statsOptions.NoStream, "no-stream", false, "Disable streaming stats and only pull the first result")
+
+ if registry.IsRemote() {
+ _ = flags.MarkHidden("latest")
+ }
+}
+
+func stats(cmd *cobra.Command, args []string) error {
+ // Validate input.
+ if err := entities.ValidatePodStatsOptions(args, &statsOptions.PodStatsOptions); err != nil {
+ return err
+ }
+
+ format := statsOptions.Format
+ doJson := strings.ToLower(format) == formats.JSONString
+ header := getPodStatsHeader(format)
+
+ for {
+ reports, err := registry.ContainerEngine().PodStats(context.Background(), args, statsOptions.PodStatsOptions)
+ if err != nil {
+ return err
+ }
+ // Print the stats in the requested format and configuration.
+ if doJson {
+ if err := printJSONPodStats(reports); err != nil {
+ return err
+ }
+ } else {
+ if !statsOptions.NoReset {
+ goterm.Clear()
+ goterm.MoveCursor(1, 1)
+ goterm.Flush()
+ }
+ if len(format) == 0 {
+ printPodStatsLines(reports)
+ } else if err := printFormattedPodStatsLines(format, reports, header); err != nil {
+ return err
+ }
+ }
+ if statsOptions.NoStream {
+ break
+ }
+ time.Sleep(time.Second)
+ }
+
+ return nil
+}
+
+func printJSONPodStats(stats []*entities.PodStatsReport) error {
+ b, err := json.MarshalIndent(&stats, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(os.Stdout, "%s\n", string(b))
+ return nil
+}
+
+func printPodStatsLines(stats []*entities.PodStatsReport) {
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
+ outFormat := "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
+ fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS")
+ for _, i := range stats {
+ if len(stats) == 0 {
+ fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--")
+ } else {
+ fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS)
+ }
+ }
+ w.Flush()
+}
+
+func printFormattedPodStatsLines(format string, stats []*entities.PodStatsReport, headerNames map[string]string) error {
+ if len(stats) == 0 {
+ return nil
+ }
+
+ // Use a tabwriter to align column format
+ w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
+ // Spit out the header if "table" is present in the format
+ if strings.HasPrefix(format, "table") {
+ hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1)
+ format = hformat
+ headerTmpl, err := template.New("header").Parse(hformat)
+ if err != nil {
+ return err
+ }
+ if err := headerTmpl.Execute(w, headerNames); err != nil {
+ return err
+ }
+ fmt.Fprintln(w, "")
+ }
+
+ // Spit out the data rows now
+ dataTmpl, err := template.New("data").Parse(format)
+ if err != nil {
+ return err
+ }
+ for _, s := range stats {
+ if err := dataTmpl.Execute(w, s); err != nil {
+ return err
+ }
+ fmt.Fprintln(w, "")
+ }
+ // Flush the writer
+ return w.Flush()
+
+}
+
+// getPodStatsHeader returns the stats header for the specified options.
+func getPodStatsHeader(format string) map[string]string {
+ headerNames := make(map[string]string)
+ if format == "" {
+ return headerNames
+ }
+ // Make a map of the field names for the headers
+ v := reflect.ValueOf(entities.PodStatsReport{})
+ t := v.Type()
+ for i := 0; i < t.NumField(); i++ {
+ split := camelcase.Split(t.Field(i).Name)
+ value := strings.ToUpper(strings.Join(split, " "))
+ switch value {
+ case "CPU", "MEM":
+ value += " %"
+ case "MEM USAGE":
+ value = "MEM USAGE / LIMIT"
+ }
+ headerNames[t.Field(i).Name] = value
+ }
+ return headerNames
+}
diff --git a/cmd/podman/pods/stop.go b/cmd/podman/pods/stop.go
index 683d9c00a..daf05d640 100644
--- a/cmd/podman/pods/stop.go
+++ b/cmd/podman/pods/stop.go
@@ -47,11 +47,10 @@ func init() {
flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running pods")
flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified pod is missing")
flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Stop the latest pod podman is aware of")
- flags.UintVarP(&timeout, "time", "t", 0, "Seconds to wait for pod stop before killing the container")
+ flags.UintVarP(&timeout, "time", "t", containerConfig.Engine.StopTimeout, "Seconds to wait for pod stop before killing the container")
if registry.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("ignore")
-
}
flags.SetNormalizeFunc(utils.AliasFlags)
}
diff --git a/cmd/podman/registry/config.go b/cmd/podman/registry/config.go
index 358f9172e..fc6eb538e 100644
--- a/cmd/podman/registry/config.go
+++ b/cmd/podman/registry/config.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"strings"
+ "sync"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/pkg/domain/entities"
@@ -19,11 +20,18 @@ const (
)
var (
- PodmanOptions entities.PodmanConfig
+ podmanOptions entities.PodmanConfig
+ podmanSync sync.Once
)
-// NewPodmanConfig creates a PodmanConfig from the environment
-func NewPodmanConfig() entities.PodmanConfig {
+// PodmanConfig returns an entities.PodmanConfig built up from
+// environment and CLI
+func PodmanConfig() *entities.PodmanConfig {
+ podmanSync.Do(newPodmanConfig)
+ return &podmanOptions
+}
+
+func newPodmanConfig() {
if err := setXdgDirs(); err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
@@ -63,7 +71,7 @@ func NewPodmanConfig() entities.PodmanConfig {
cfg.Network.NetworkConfigDir = ""
}
- return entities.PodmanConfig{Config: cfg, EngineMode: mode}
+ podmanOptions = entities.PodmanConfig{Config: cfg, EngineMode: mode}
}
// SetXdgDirs ensures the XDG_RUNTIME_DIR env and XDG_CONFIG_HOME variables are set.
diff --git a/cmd/podman/registry/json.go b/cmd/podman/registry/json.go
new file mode 100644
index 000000000..f25406c3c
--- /dev/null
+++ b/cmd/podman/registry/json.go
@@ -0,0 +1,20 @@
+package registry
+
+import (
+ "sync"
+
+ jsoniter "github.com/json-iterator/go"
+)
+
+var (
+ json jsoniter.API
+ jsonSync sync.Once
+)
+
+// JsonLibrary provides a "encoding/json" compatible API
+func JsonLibrary() jsoniter.API {
+ jsonSync.Do(func() {
+ json = jsoniter.ConfigCompatibleWithStandardLibrary
+ })
+ return json
+}
diff --git a/cmd/podman/registry/registry.go b/cmd/podman/registry/registry.go
index 1c5e5d21b..69e2babfc 100644
--- a/cmd/podman/registry/registry.go
+++ b/cmd/podman/registry/registry.go
@@ -5,7 +5,6 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra"
- "github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -49,8 +48,8 @@ func ImageEngine() entities.ImageEngine {
// NewImageEngine is a wrapper for building an ImageEngine to be used for PreRunE functions
func NewImageEngine(cmd *cobra.Command, args []string) (entities.ImageEngine, error) {
if imageEngine == nil {
- PodmanOptions.FlagSet = cmd.Flags()
- engine, err := infra.NewImageEngine(PodmanOptions)
+ podmanOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewImageEngine(&podmanOptions)
if err != nil {
return nil, err
}
@@ -66,8 +65,8 @@ func ContainerEngine() entities.ContainerEngine {
// NewContainerEngine is a wrapper for building an ContainerEngine to be used for PreRunE functions
func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEngine, error) {
if containerEngine == nil {
- PodmanOptions.FlagSet = cmd.Flags()
- engine, err := infra.NewContainerEngine(PodmanOptions)
+ podmanOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewContainerEngine(&podmanOptions)
if err != nil {
return nil, err
}
@@ -76,21 +75,6 @@ func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEn
return containerEngine, nil
}
-func SubCommandExists(cmd *cobra.Command, args []string) error {
- if len(args) > 0 {
- return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0])
- }
- return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath())
-}
-
-// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
-func IdOrLatestArgs(cmd *cobra.Command, args []string) error {
- if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) {
- return errors.New(`command requires a name, id or the "--latest" flag`)
- }
- return nil
-}
-
type PodmanOptionsKey struct{}
func Context() context.Context {
@@ -101,7 +85,7 @@ func Context() context.Context {
}
func ContextWithOptions(ctx context.Context) context.Context {
- cliCtx = context.WithValue(ctx, PodmanOptionsKey{}, PodmanOptions)
+ cliCtx = context.WithValue(ctx, PodmanOptionsKey{}, podmanOptions)
return cliCtx
}
diff --git a/cmd/podman/registry/remote.go b/cmd/podman/registry/remote.go
index 5378701e7..95870750e 100644
--- a/cmd/podman/registry/remote.go
+++ b/cmd/podman/registry/remote.go
@@ -5,5 +5,5 @@ import (
)
func IsRemote() bool {
- return PodmanOptions.EngineMode == entities.TunnelMode
+ return podmanOptions.EngineMode == entities.TunnelMode
}
diff --git a/cmd/podman/report/diff.go b/cmd/podman/report/diff.go
index b36189d75..0730f06e8 100644
--- a/cmd/podman/report/diff.go
+++ b/cmd/podman/report/diff.go
@@ -6,7 +6,6 @@ import (
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/storage/pkg/archive"
- jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
)
@@ -31,7 +30,7 @@ func ChangesToJSON(diffs *entities.DiffReport) error {
}
}
- json := jsoniter.ConfigCompatibleWithStandardLibrary
+ // Pull in configured json library
enc := json.NewEncoder(os.Stdout)
return enc.Encode(body)
}
diff --git a/cmd/podman/report/report.go b/cmd/podman/report/report.go
new file mode 100644
index 000000000..8392f10e0
--- /dev/null
+++ b/cmd/podman/report/report.go
@@ -0,0 +1,6 @@
+package report
+
+import "github.com/containers/libpod/cmd/podman/registry"
+
+// Pull in configured json library
+var json = registry.JsonLibrary()
diff --git a/cmd/podman/root.go b/cmd/podman/root.go
index 259e10c55..375faf8b1 100644
--- a/cmd/podman/root.go
+++ b/cmd/podman/root.go
@@ -9,6 +9,7 @@ import (
"strings"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/tracing"
@@ -34,7 +35,7 @@ Description:
// UsageTemplate is the usage template for podman commands
// This blocks the displaying of the global options. The main podman
// command should not use this.
-const usageTemplate = `Usage(v2):{{if (and .Runnable (not .HasAvailableSubCommands))}}
+const usageTemplate = `Usage:{{if (and .Runnable (not .HasAvailableSubCommands))}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
@@ -60,7 +61,7 @@ var (
SilenceErrors: true,
TraverseChildren: true,
PersistentPreRunE: persistentPreRunE,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
PersistentPostRunE: persistentPostRunE,
Version: version.Version,
}
@@ -77,12 +78,16 @@ func init() {
syslogHook,
)
- rootFlags(registry.PodmanOptions, rootCmd.PersistentFlags())
+ rootFlags(registry.PodmanConfig(), rootCmd.PersistentFlags())
+
+ // "version" is a local flag to avoid collisions with sub-commands that use "-v"
+ var dummyVersion bool
+ rootCmd.Flags().BoolVarP(&dummyVersion, "version", "v", false, "Version of Podman")
}
func Execute() {
if err := rootCmd.ExecuteContext(registry.GetContextWithOptions()); err != nil {
- logrus.Error(err)
+ fmt.Fprintln(os.Stderr, "Error:", err.Error())
} else if registry.GetExitCode() == registry.ExecErrorCodeGeneric {
// The exitCode modified from registry.ExecErrorCodeGeneric,
// indicates an application
@@ -96,11 +101,9 @@ func Execute() {
func persistentPreRunE(cmd *cobra.Command, args []string) error {
// TODO: Remove trace statement in podman V2.1
- logrus.Debugf("Called %s.PersistentPreRunE()", cmd.Name())
+ logrus.Debugf("Called %s.PersistentPreRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
- // Update PodmanOptions now that we "know" more
- // TODO: pass in path overriding configuration file
- registry.PodmanOptions = registry.NewPodmanConfig()
+ cfg := registry.PodmanConfig()
// Prep the engines
if _, err := registry.NewImageEngine(cmd, args); err != nil {
@@ -111,10 +114,10 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
}
if cmd.Flag("cpu-profile").Changed {
- f, err := os.Create(registry.PodmanOptions.CpuProfile)
+ f, err := os.Create(cfg.CpuProfile)
if err != nil {
return errors.Wrapf(err, "unable to create cpu profiling file %s",
- registry.PodmanOptions.CpuProfile)
+ cfg.CpuProfile)
}
if err := pprof.StartCPUProfile(f); err != nil {
return err
@@ -124,11 +127,11 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
if cmd.Flag("trace").Changed {
tracer, closer := tracing.Init("podman")
opentracing.SetGlobalTracer(tracer)
- registry.PodmanOptions.SpanCloser = closer
+ cfg.SpanCloser = closer
- registry.PodmanOptions.Span = tracer.StartSpan("before-context")
- registry.PodmanOptions.SpanCtx = opentracing.ContextWithSpan(registry.Context(), registry.PodmanOptions.Span)
- opentracing.StartSpanFromContext(registry.PodmanOptions.SpanCtx, cmd.Name())
+ cfg.Span = tracer.StartSpan("before-context")
+ cfg.SpanCtx = opentracing.ContextWithSpan(registry.Context(), cfg.Span)
+ opentracing.StartSpanFromContext(cfg.SpanCtx, cmd.Name())
}
// Setup Rootless environment, IFF:
@@ -147,15 +150,19 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
func persistentPostRunE(cmd *cobra.Command, args []string) error {
// TODO: Remove trace statement in podman V2.1
- logrus.Debugf("Called %s.PersistentPostRunE()", cmd.Name())
+ logrus.Debugf("Called %s.PersistentPostRunE(%s)", cmd.Name(), strings.Join(os.Args, " "))
+ cfg := registry.PodmanConfig()
if cmd.Flag("cpu-profile").Changed {
pprof.StopCPUProfile()
}
if cmd.Flag("trace").Changed {
- registry.PodmanOptions.Span.Finish()
- registry.PodmanOptions.SpanCloser.Close()
+ cfg.Span.Finish()
+ cfg.SpanCloser.Close()
}
+
+ registry.ImageEngine().Shutdown(registry.Context())
+ registry.ContainerEngine().Shutdown(registry.Context())
return nil
}
@@ -199,16 +206,11 @@ func syslogHook() {
}
}
-func rootFlags(opts entities.PodmanConfig, flags *pflag.FlagSet) {
+func rootFlags(opts *entities.PodmanConfig, flags *pflag.FlagSet) {
// V2 flags
flags.StringVarP(&opts.Uri, "remote", "r", "", "URL to access Podman service")
flags.StringSliceVar(&opts.Identities, "identity", []string{}, "path to SSH identity file")
- // Override default --help information of `--version` global flag
- // TODO: restore -v option for version without breaking -v for volumes
- var dummyVersion bool
- flags.BoolVar(&dummyVersion, "version", false, "Version of Podman")
-
cfg := opts.Config
flags.StringVar(&cfg.Engine.CgroupManager, "cgroup-manager", cfg.Engine.CgroupManager, opts.CGroupUsage)
flags.StringVar(&opts.CpuProfile, "cpu-profile", "", "Path for the cpu profiling results")
diff --git a/cmd/podman/system/events.go b/cmd/podman/system/events.go
index 3c1943b55..6aae62dc0 100644
--- a/cmd/podman/system/events.go
+++ b/cmd/podman/system/events.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -18,7 +19,7 @@ var (
eventsDescription = "Monitor podman events"
eventsCommand = &cobra.Command{
Use: "events",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Show podman events",
Long: eventsDescription,
RunE: eventsCmd,
diff --git a/cmd/podman/system/info.go b/cmd/podman/system/info.go
index aa0a66ffc..26be794c5 100644
--- a/cmd/podman/system/info.go
+++ b/cmd/podman/system/info.go
@@ -1,15 +1,15 @@
package system
import (
- "encoding/json"
"fmt"
"os"
"text/template"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/ghodss/yaml"
"github.com/spf13/cobra"
- "gopkg.in/yaml.v2"
)
var (
@@ -19,7 +19,7 @@ var (
`
infoCommand = &cobra.Command{
Use: "info",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Long: infoDescription,
Short: "Display podman system information",
RunE: info,
diff --git a/cmd/podman/system/service.go b/cmd/podman/system/service.go
index fa1a33faa..f4b91dd78 100644
--- a/cmd/podman/system/service.go
+++ b/cmd/podman/system/service.go
@@ -2,8 +2,10 @@ package system
import (
"fmt"
+ "net/url"
"os"
"path/filepath"
+ "syscall"
"time"
"github.com/containers/libpod/cmd/podman/registry"
@@ -57,7 +59,24 @@ func service(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
- logrus.Infof("using API endpoint: \"%s\"", apiURI)
+ logrus.Infof("using API endpoint: '%s'", apiURI)
+
+ // Clean up any old existing unix domain socket
+ if len(apiURI) > 0 {
+ uri, err := url.Parse(apiURI)
+ if err != nil {
+ return err
+ }
+
+ // socket activation uses a unix:// socket in the shipped unit files but apiURI is coded as "" at this layer.
+ if "unix" == uri.Scheme && !registry.IsRemote() {
+ if err := syscall.Unlink(uri.Path); err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ mask := syscall.Umask(0177)
+ defer syscall.Umask(mask)
+ }
+ }
opts := entities.ServiceOptions{
URI: apiURI,
@@ -71,11 +90,11 @@ func service(cmd *cobra.Command, args []string) error {
logrus.Warn("This function is EXPERIMENTAL")
fmt.Fprintf(os.Stderr, "This function is EXPERIMENTAL.\n")
- return registry.ContainerEngine().RestService(registry.GetContext(), opts)
+
+ return restService(opts, cmd.Flags(), registry.PodmanConfig())
}
func resolveApiURI(_url []string) (string, error) {
-
// When determining _*THE*_ listening endpoint --
// 1) User input wins always
// 2) systemd socket activation
@@ -83,14 +102,15 @@ func resolveApiURI(_url []string) (string, error) {
// 4) if varlink -- adapter.DefaultVarlinkAddress
// 5) lastly adapter.DefaultAPIAddress
- if _url == nil {
+ if len(_url) == 0 {
if v, found := os.LookupEnv("PODMAN_SOCKET"); found {
+ logrus.Debugf("PODMAN_SOCKET='%s' used to determine API endpoint", v)
_url = []string{v}
}
}
switch {
- case len(_url) > 0:
+ case len(_url) > 0 && _url[0] != "":
return _url[0], nil
case systemd.SocketActivated():
logrus.Info("using systemd socket activation to determine API endpoint")
diff --git a/cmd/podman/system/service_abi.go b/cmd/podman/system/service_abi.go
new file mode 100644
index 000000000..3da6ccfc7
--- /dev/null
+++ b/cmd/podman/system/service_abi.go
@@ -0,0 +1,57 @@
+// +build ABISupport
+
+package system
+
+import (
+ "context"
+ "net"
+ "strings"
+
+ api "github.com/containers/libpod/pkg/api/server"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+ var (
+ listener *net.Listener
+ err error
+ )
+
+ if opts.URI != "" {
+ fields := strings.Split(opts.URI, ":")
+ if len(fields) == 1 {
+ return errors.Errorf("%s is an invalid socket destination", opts.URI)
+ }
+ address := strings.Join(fields[1:], ":")
+ l, err := net.Listen(fields[0], address)
+ if err != nil {
+ return errors.Wrapf(err, "unable to create socket %s", opts.URI)
+ }
+ listener = &l
+ }
+
+ rt, err := infra.GetRuntime(context.Background(), flags, cfg)
+ if err != nil {
+ return err
+ }
+
+ server, err := api.NewServerWithSettings(rt, opts.Timeout, listener)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err := server.Shutdown(); err != nil {
+ logrus.Warnf("Error when stopping API service: %s", err)
+ }
+ }()
+
+ err = server.Serve()
+ if listener != nil {
+ _ = (*listener).Close()
+ }
+ return err
+}
diff --git a/cmd/podman/system/service_unsupported.go b/cmd/podman/system/service_unsupported.go
new file mode 100644
index 000000000..95f8189f6
--- /dev/null
+++ b/cmd/podman/system/service_unsupported.go
@@ -0,0 +1,14 @@
+// +build !ABISupport
+
+package system
+
+import (
+ "errors"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/pflag"
+)
+
+func restService(opts entities.ServiceOptions, flags *pflag.FlagSet, cfg *entities.PodmanConfig) error {
+ return errors.New("not supported")
+}
diff --git a/cmd/podman/system/system.go b/cmd/podman/system/system.go
index 6d8c9ebc5..d9691ad2a 100644
--- a/cmd/podman/system/system.go
+++ b/cmd/podman/system/system.go
@@ -2,18 +2,22 @@ package system
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _system_
systemCmd = &cobra.Command{
Use: "system",
Short: "Manage podman",
Long: "Manage podman",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/cmd/podman/system/version.go b/cmd/podman/system/version.go
index 5d3874de3..065eef309 100644
--- a/cmd/podman/system/version.go
+++ b/cmd/podman/system/version.go
@@ -10,6 +10,7 @@ import (
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -19,7 +20,7 @@ import (
var (
versionCommand = &cobra.Command{
Use: "version",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Display the Podman Version Information",
RunE: version,
Annotations: map[string]string{
@@ -55,14 +56,14 @@ func version(cmd *cobra.Command, args []string) error {
// TODO we need to discuss how to implement
// this more. current endpoints dont have a
// version endpoint. maybe we use info?
- //if remote {
+ // if remote {
// v.Server, err = getRemoteVersion(c)
// if err != nil {
// return err
// }
- //} else {
+ // } else {
v.Server = v.Client
- //}
+ // }
versionOutputFormat := versionFormat
if versionOutputFormat != "" {
diff --git a/cmd/podman/utils/alias.go b/cmd/podman/utils/alias.go
index 54b3c5e89..e484461c5 100644
--- a/cmd/podman/utils/alias.go
+++ b/cmd/podman/utils/alias.go
@@ -2,7 +2,7 @@ package utils
import "github.com/spf13/pflag"
-// AliasFlags is a function to handle backwards compatability with old flags
+// AliasFlags is a function to handle backwards compatibility with old flags
func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "healthcheck-command":
diff --git a/cmd/podman/utils/utils.go b/cmd/podman/utils/utils.go
new file mode 100644
index 000000000..c7d105ba4
--- /dev/null
+++ b/cmd/podman/utils/utils.go
@@ -0,0 +1,22 @@
+package utils
+
+import "os"
+
+// IsDir returns true if the specified path refers to a directory.
+func IsDir(path string) bool {
+ file, err := os.Stat(path)
+ if err != nil {
+ return false
+ }
+ return file.IsDir()
+}
+
+// FileExists returns true if path refers to an existing file.
+func FileExists(path string) bool {
+ file, err := os.Stat(path)
+ // All errors return file == nil
+ if err != nil {
+ return false
+ }
+ return !file.IsDir()
+}
diff --git a/cmd/podman/validate/args.go b/cmd/podman/validate/args.go
new file mode 100644
index 000000000..14b4d7897
--- /dev/null
+++ b/cmd/podman/validate/args.go
@@ -0,0 +1,32 @@
+package validate
+
+import (
+ "fmt"
+
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+// NoArgs returns an error if any args are included.
+func NoArgs(cmd *cobra.Command, args []string) error {
+ if len(args) > 0 {
+ return fmt.Errorf("`%s` takes no arguments", cmd.CommandPath())
+ }
+ return nil
+}
+
+// SubCommandExists returns an error if no sub command is provided
+func SubCommandExists(cmd *cobra.Command, args []string) error {
+ if len(args) > 0 {
+ return errors.Errorf("unrecognized command `%[1]s %[2]s`\nTry '%[1]s --help' for more information.", cmd.CommandPath(), args[0])
+ }
+ return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath())
+}
+
+// IdOrLatestArgs used to validate a nameOrId was provided or the "--latest" flag
+func IdOrLatestArgs(cmd *cobra.Command, args []string) error {
+ if len(args) > 1 || (len(args) == 0 && !cmd.Flag("latest").Changed) {
+ return fmt.Errorf("`%s` requires a name, id or the \"--latest\" flag", cmd.CommandPath())
+ }
+ return nil
+}
diff --git a/cmd/podman/validate/choice.go b/cmd/podman/validate/choice.go
new file mode 100644
index 000000000..572c5f4a5
--- /dev/null
+++ b/cmd/podman/validate/choice.go
@@ -0,0 +1,46 @@
+package validate
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Honors cobra.Value interface
+type choiceValue struct {
+ value *string
+ choices []string
+}
+
+// ChoiceValue may be used in cobra FlagSet methods Var/VarP/VarPF() to select from a set of values
+//
+// Example:
+// created := validate.ChoiceValue(&opts.Sort, "command", "created", "id", "image", "names", "runningfor", "size", "status")
+// flags.Var(created, "sort", "Sort output by: "+created.Choices())
+func ChoiceValue(p *string, choices ...string) *choiceValue {
+ return &choiceValue{
+ value: p,
+ choices: choices,
+ }
+}
+
+func (c *choiceValue) String() string {
+ return *c.value
+}
+
+func (c *choiceValue) Set(value string) error {
+ for _, v := range c.choices {
+ if v == value {
+ *c.value = value
+ return nil
+ }
+ }
+ return fmt.Errorf("%q is not a valid value. Choose from: %q", value, c.Choices())
+}
+
+func (c *choiceValue) Choices() string {
+ return strings.Join(c.choices, ", ")
+}
+
+func (c *choiceValue) Type() string {
+ return "choice"
+}
diff --git a/cmd/podman/volumes/create.go b/cmd/podman/volumes/create.go
index df0731791..1bec8d0e7 100644
--- a/cmd/podman/volumes/create.go
+++ b/cmd/podman/volumes/create.go
@@ -40,7 +40,7 @@ func init() {
Parent: volumeCmd,
})
flags := createCommand.Flags()
- flags.StringVar(&createOpts.Driver, "driver", "", "Specify volume driver name (default local)")
+ flags.StringVar(&createOpts.Driver, "driver", "local", "Specify volume driver name")
flags.StringSliceVarP(&opts.Label, "label", "l", []string{}, "Set metadata for a volume (default [])")
flags.StringArrayVarP(&opts.Opts, "opt", "o", []string{}, "Set driver specific options (default [])")
}
diff --git a/cmd/podman/volumes/inspect.go b/cmd/podman/volumes/inspect.go
index 57e773aef..79f65ea4a 100644
--- a/cmd/podman/volumes/inspect.go
+++ b/cmd/podman/volumes/inspect.go
@@ -1,10 +1,10 @@
package volumes
import (
- "encoding/json"
"fmt"
"html/template"
"os"
+ "strings"
"github.com/containers/buildah/pkg/formats"
"github.com/containers/libpod/cmd/podman/registry"
@@ -61,7 +61,11 @@ func inspect(cmd *cobra.Command, args []string) error {
}
fmt.Println(string(jsonOut))
default:
- tmpl, err := template.New("volumeInspect").Parse(inspectFormat)
+ if !strings.HasSuffix(inspectFormat, "\n") {
+ inspectFormat += "\n"
+ }
+ format := "{{range . }}" + inspectFormat + "{{end}}"
+ tmpl, err := template.New("volumeInspect").Parse(format)
if err != nil {
return err
}
diff --git a/cmd/podman/volumes/list.go b/cmd/podman/volumes/list.go
index fd89db01f..72bf9f25b 100644
--- a/cmd/podman/volumes/list.go
+++ b/cmd/podman/volumes/list.go
@@ -2,6 +2,7 @@ package volumes
import (
"context"
+ "fmt"
"html/template"
"io"
"os"
@@ -9,6 +10,7 @@ import (
"text/tabwriter"
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -23,7 +25,7 @@ and the output format can be changed to JSON or a user specified Go template.`
lsCommand = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "List volumes",
Long: volumeLsDescription,
RunE: list,
@@ -57,6 +59,9 @@ func list(cmd *cobra.Command, args []string) error {
if cliOpts.Quiet && cmd.Flag("format").Changed {
return errors.New("quiet and format flags cannot be used together")
}
+ if len(cliOpts.Filter) > 0 {
+ lsOpts.Filter = make(map[string][]string)
+ }
for _, f := range cliOpts.Filter {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
@@ -68,6 +73,13 @@ func list(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
+ if cliOpts.Format == "json" {
+ return outputJSON(responses)
+ }
+
+ if len(responses) < 1 {
+ return nil
+ }
// "\t" from the command line is not being recognized as a tab
// replacing the string "\t" to a tab character if the user passes in "\t"
cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1)
@@ -96,3 +108,12 @@ func list(cmd *cobra.Command, args []string) error {
}
return nil
}
+
+func outputJSON(vols []*entities.VolumeListReport) error {
+ b, err := json.MarshalIndent(vols, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(b))
+ return nil
+}
diff --git a/cmd/podman/volumes/prune.go b/cmd/podman/volumes/prune.go
index 197a9da9b..2c3ed88f3 100644
--- a/cmd/podman/volumes/prune.go
+++ b/cmd/podman/volumes/prune.go
@@ -9,6 +9,7 @@ import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -21,7 +22,7 @@ var (
Note all data will be destroyed.`
pruneCommand = &cobra.Command{
Use: "prune",
- Args: cobra.NoArgs,
+ Args: validate.NoArgs,
Short: "Remove all unused volumes",
Long: volumePruneDescription,
RunE: prune,
diff --git a/cmd/podman/volumes/volume.go b/cmd/podman/volumes/volume.go
index 06943da62..3e90d178c 100644
--- a/cmd/podman/volumes/volume.go
+++ b/cmd/podman/volumes/volume.go
@@ -2,18 +2,22 @@ package volumes
import (
"github.com/containers/libpod/cmd/podman/registry"
+ "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
+ // Pull in configured json library
+ json = registry.JsonLibrary()
+
// Command: podman _volume_
volumeCmd = &cobra.Command{
Use: "volume",
Short: "Manage volumes",
Long: "Volumes are created in and can be shared between containers",
TraverseChildren: true,
- RunE: registry.SubCommandExists,
+ RunE: validate.SubCommandExists,
}
)
diff --git a/completions/bash/podman b/completions/bash/podman
index 6997db3b5..d6e9408c6 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -1733,6 +1733,84 @@ _podman_logs() {
esac
}
+_podman_manifest() {
+ local boolean_options="
+ --help
+ -h
+ "
+ subcommands="
+ add
+ create
+ inspect
+ "
+ __podman_subcommands "$subcommands" && return
+
+ case "$cur" in
+ -*)
+ COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
+ ;;
+ esac
+}
+
+_podman_manifest_add() {
+ local options_with_args="
+ --annotation
+ --arch
+ --features
+ --os
+ --os-version
+ --variant
+ "
+
+ local boolean_options="
+ --all
+ --help
+ -h
+ "
+
+ _complete_ "$options_with_args" "$boolean_options"
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_images --id
+ ;;
+ esac
+}
+
+_podman_manifest_create() {
+ local boolean_options="
+ --all
+ --help
+ -h
+ "
+
+ _complete_ "$boolean_options"
+}
+
+_podman_manifest_inspect() {
+ local options_with_args="
+ "
+
+ local boolean_options="
+ "
+
+ _complete_ "$options_with_args" "$boolean_options"
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_images --id
+
+ ;;
+ esac
+}
+
_podman_pull() {
local options_with_args="
--authfile
@@ -3356,6 +3434,7 @@ _podman_podman() {
login
logout
logs
+ manifest
mount
pause
pod
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
index 2031432b9..dd4f66f56 100644
--- a/contrib/cirrus/lib.sh
+++ b/contrib/cirrus/lib.sh
@@ -6,6 +6,11 @@
# Global details persist here
source /etc/environment # not always loaded under all circumstances
+# Automation environment doesn't automatically load for Ubuntu 18
+if [[ -r '/usr/share/automation/environment' ]]; then
+ source '/usr/share/automation/environment'
+fi
+
# Under some contexts these values are not set, make sure they are.
export USER="$(whoami)"
export HOME="$(getent passwd $USER | cut -d : -f 6)"
@@ -34,7 +39,6 @@ PACKER_BASE=${PACKER_BASE:-./contrib/cirrus/packer}
# Important filepaths
SETUP_MARKER_FILEPATH="${SETUP_MARKER_FILEPATH:-/var/tmp/.setup_environment_sh_complete}"
AUTHOR_NICKS_FILEPATH="${CIRRUS_WORKING_DIR}/${SCRIPT_BASE}/git_authors_to_irc_nicks.csv"
-BUILDAH_PACKAGES_FILEPATH="./contrib/cirrus/packages.sh" # in buildah repo.
# Log remote-client system test varlink output here
export VARLINK_LOG=/var/tmp/varlink.log
@@ -60,23 +64,28 @@ PACKER_VER="1.4.2"
# CSV of cache-image names to build (see $PACKER_BASE/libpod_images.json)
# Base-images rarely change, define them here so they're out of the way.
-export PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,ubuntu-19,fedora-31,fedora-30}"
+export PACKER_BUILDS="${PACKER_BUILDS:-ubuntu-18,ubuntu-19,fedora-32,fedora-31}"
# Manually produced base-image names (see $SCRIPT_BASE/README.md)
export UBUNTU_BASE_IMAGE="ubuntu-1910-eoan-v20200211"
export PRIOR_UBUNTU_BASE_IMAGE="ubuntu-1804-bionic-v20200218"
# Manually produced base-image names (see $SCRIPT_BASE/README.md)
-export FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1578586410"
-export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-30-1-2-1578586410"
+export FEDORA_BASE_IMAGE="fedora-cloud-base-32-n-0-1586202964"
+export PRIOR_FEDORA_BASE_IMAGE="fedora-cloud-base-31-1-9-1586202964"
export BUILT_IMAGE_SUFFIX="${BUILT_IMAGE_SUFFIX:--$CIRRUS_REPO_NAME-${CIRRUS_BUILD_ID}}"
# IN_PODMAN container image
IN_PODMAN_IMAGE="quay.io/libpod/in_podman:$DEST_BRANCH"
# Image for uploading releases
UPLDREL_IMAGE="quay.io/libpod/upldrel:master"
+# This is needed under some environments/contexts
+SUDO=''
+[[ "$UID" -eq 0 ]] || \
+ SUDO='sudo -E'
+
# Avoid getting stuck waiting for user input
export DEBIAN_FRONTEND="noninteractive"
-SUDOAPTGET="ooe.sh sudo -E apt-get -qq --yes"
-SUDOAPTADD="ooe.sh sudo -E add-apt-repository --yes"
+SUDOAPTGET="$SUDO apt-get -qq --yes"
+SUDOAPTADD="$SUDO add-apt-repository --yes"
# Regex that finds enabled periodic apt configuration items
PERIODIC_APT_RE='^(APT::Periodic::.+")1"\;'
# Short-cuts for retrying/timeout calls
@@ -110,6 +119,9 @@ OS_REL_VER="${OS_RELEASE_ID}-${OS_RELEASE_VER}"
# Type of filesystem used for cgroups
CG_FS_TYPE="$(stat -f -c %T /sys/fs/cgroup)"
+# When building images, the version of automation tooling to install
+INSTALL_AUTOMATION_VERSION=1.1.3
+
# Installed into cache-images, supports overrides
# by user-data in case of breakage or for debugging.
CUSTOM_CLOUD_CONFIG_DEFAULTS="$GOSRC/$PACKER_BASE/cloud-init/$OS_RELEASE_ID/cloud.cfg.d"
@@ -355,25 +367,18 @@ setup_rootless() {
die 11 "Timeout exceeded waiting for localhost ssh capability"
}
-# Helper/wrapper script to only show stderr/stdout on non-zero exit
-install_ooe() {
- req_env_var SCRIPT_BASE
- echo "Installing script to mask stdout/stderr unless non-zero exit."
- sudo install -D -m 755 "$GOSRC/$SCRIPT_BASE/ooe.sh" /usr/local/bin/ooe.sh
-}
-
# Grab a newer version of git from software collections
# https://www.softwarecollections.org/en/
# and use it with a wrapper
install_scl_git() {
echo "Installing SoftwareCollections updated 'git' version."
- ooe.sh sudo yum -y install rh-git29
- cat << "EOF" | sudo tee /usr/bin/git
+ ooe.sh $SUDO yum -y install rh-git29
+ cat << "EOF" | $SUDO tee /usr/bin/git
#!/bin/bash
scl enable rh-git29 -- git $@
EOF
- sudo chmod 755 /usr/bin/git
+ $SUDO chmod 755 /usr/bin/git
}
install_test_configs() {
@@ -389,8 +394,7 @@ install_test_configs() {
install -v -D -m 644 ./test/registries.conf /etc/containers/
}
-# Remove all files (except conmon, for now) provided by the distro version of podman.
-# Except conmon, for now as it's expected to eventually be packaged separately.
+# Remove all files provided by the distro version of podman.
# All VM cache-images used for testing include the distro podman because (1) it's
# required for podman-in-podman testing and (2) it somewhat simplifies the task
# of pulling in necessary prerequisites packages as the set can change over time.
@@ -416,9 +420,9 @@ remove_packaged_podman_files() {
if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]
then
- LISTING_CMD="sudo -E dpkg-query -L podman"
+ LISTING_CMD="$SUDO dpkg-query -L podman"
else
- LISTING_CMD='sudo rpm -ql podman'
+ LISTING_CMD='$SUDO rpm -ql podman'
fi
# yum/dnf/dpkg may list system directories, only remove files
@@ -426,7 +430,7 @@ remove_packaged_podman_files() {
do
# Sub-directories may contain unrelated/valuable stuff
if [[ -d "$fullpath" ]]; then continue; fi
- ooe.sh sudo rm -vf "$fullpath"
+ ooe.sh $SUDO rm -vf "$fullpath"
done
# Be super extra sure and careful vs performant and completely safe
@@ -449,63 +453,60 @@ systemd_banish() {
$GOSRC/$PACKER_BASE/systemd_banish.sh
}
-install_buildah_packages() {
- git clone https://github.com/containers/buildah.git /tmp/buildah
- if [[ -r "$BUILDAH_PACKAGES_FILEPATH" ]]; then
- source "$BUILDAH_PACKAGES_FILEPATH"
- req_env_var UBUNTU_BUILDAH_PACKAGES FEDORA_BUILDAH_PACKAGES OS_RELEASE_ID
- case "$OS_RELEASE_ID" in
- fedora)
- $BIGTO ooe.sh sudo dnf install -y ${FEDORA_BUILDAH_PACKAGES[@]}
- ;;
- ubuntu)
- $LILTO $SUDOAPTGET update
- $BIGTO $SUDOAPTGET install ${UBUNTU_BUILDAH_PACKAGES[@]}
- ;;
- *) bad_os_id_ver ;;
- esac
- else
- warn "Could not find $BUILDAH_PACKAGES_FILEPATH in buildah repository root."
+# This can be removed when the kernel bug fix is included in Fedora
+workaround_bfq_bug() {
+ if [[ "$OS_RELEASE_ID" == "fedora" ]] && [[ $OS_RELEASE_VER -le 32 ]]; then
+ warn "Switching io scheduler to 'deadline' to avoid RHBZ 1767539"
+ warn "aka https://bugzilla.kernel.org/show_bug.cgi?id=205447"
+ echo "mq-deadline" | sudo tee /sys/block/sda/queue/scheduler > /dev/null
+ echo -n "IO Scheduler set to: "
+ $SUDO cat /sys/block/sda/queue/scheduler
fi
}
+# Warning: DO NOT USE.
+# This is called by other functions as the very last step during the VM Image build
+# process. It's purpose is to "reset" the image, so all the first-boot operations
+# happen at test runtime (like generating new ssh host keys, resizing partitions, etc.)
_finalize() {
set +e # Don't fail at the very end
if [[ -d "$CUSTOM_CLOUD_CONFIG_DEFAULTS" ]]
then
echo "Installing custom cloud-init defaults"
- sudo cp -v "$CUSTOM_CLOUD_CONFIG_DEFAULTS"/* /etc/cloud/cloud.cfg.d/
+ $SUDO cp -v "$CUSTOM_CLOUD_CONFIG_DEFAULTS"/* /etc/cloud/cloud.cfg.d/
else
echo "Could not find any files in $CUSTOM_CLOUD_CONFIG_DEFAULTS"
fi
echo "Re-initializing so next boot does 'first-boot' setup again."
cd /
- sudo rm -rf /var/lib/cloud/instanc*
- sudo rm -rf /root/.ssh/*
- sudo rm -rf /etc/ssh/*key*
- sudo rm -rf /etc/ssh/moduli
- sudo rm -rf /home/*
- sudo rm -rf /tmp/*
- sudo rm -rf /tmp/.??*
- sudo sync
- sudo fstrim -av
+ $SUDO rm -rf /var/lib/cloud/instanc*
+ $SUDO rm -rf /root/.ssh/*
+ $SUDO rm -rf /etc/ssh/*key*
+ $SUDO rm -rf /etc/ssh/moduli
+ $SUDO rm -rf /home/*
+ $SUDO rm -rf /tmp/*
+ $SUDO rm -rf /tmp/.??*
+ $SUDO sync
+ $SUDO fstrim -av
}
+# Called during VM Image setup, not intended for general use.
rh_finalize() {
set +e # Don't fail at the very end
echo "Resetting to fresh-state for usage as cloud-image."
PKG=$(type -P dnf || type -P yum || echo "")
- sudo $PKG clean all
- sudo rm -rf /var/cache/{yum,dnf}
- sudo rm -f /etc/udev/rules.d/*-persistent-*.rules
- sudo touch /.unconfigured # force firstboot to run
+ $SUDO $PKG clean all
+ $SUDO rm -rf /var/cache/{yum,dnf}
+ $SUDO rm -f /etc/udev/rules.d/*-persistent-*.rules
+ $SUDO touch /.unconfigured # force firstboot to run
_finalize
}
+# Called during VM Image setup, not intended for general use.
ubuntu_finalize() {
set +e # Don't fail at the very end
echo "Resetting to fresh-state for usage as cloud-image."
$LILTO $SUDOAPTGET autoremove
- sudo rm -rf /var/cache/apt
+ $SUDO rm -rf /var/cache/apt
_finalize
}
diff --git a/contrib/cirrus/logformatter b/contrib/cirrus/logformatter
index 738d2e19d..4bfe7b97f 100755
--- a/contrib/cirrus/logformatter
+++ b/contrib/cirrus/logformatter
@@ -52,12 +52,14 @@ a.codelink:hover { background: #000; color: #999; }
a.timing { text-decoration: none; }
/* BATS styles */
-.bats-ok { color: #393; }
-.bats-notok { color: #F00; font-weight: bold; }
-.bats-skip { color: #F90; }
+.bats-passed { color: #393; }
+.bats-failed { color: #F00; font-weight: bold; }
+.bats-skipped { color: #F90; }
.bats-log { color: #900; }
.bats-log-esm { color: #b00; font-weight: bold; }
+.bats-summary { font-size: 150%; }
+
/* error titles: display next to timestamp, not on separate line */
h2 { display: inline; }
END_CSS
@@ -169,7 +171,7 @@ window.addEventListener("load", scrollToBottom, false);
</script>
</head>
<body>
-<pre>
+<pre> <!-- begin processed output -->
END_HTML
# State variables
@@ -181,6 +183,7 @@ END_HTML
my $after_divider = 0; # Count of lines after seeing '-----'
my $current_output; # for removing duplication
my $looks_like_bats; # binary flag: for detecting BATS results
+ my %bats_count; # For summary line: count of pass/fail/skip
# Main loop: read input, one line at a time, and write out reformatted
LINE:
@@ -221,15 +224,16 @@ END_HTML
}
# BATS handling (used also for apiv2 tests, which emit TAP output)
- if ($line =~ /^1\.\.\d+$/ || $line =~ m!/test-apiv2!) {
+ if ($line =~ /^1\.\.(\d+)$/ || $line =~ m!/test-apiv2!) {
$looks_like_bats = 1;
+ $bats_count{expected_total} = $1;
}
if ($looks_like_bats) {
my $css;
- if ($line =~ /^ok\s.*\s# skip/) { $css = 'skip' }
- elsif ($line =~ /^ok\s/) { $css = 'ok' }
- elsif ($line =~ /^not\s+ok\s/) { $css = 'notok' }
+ if ($line =~ /^ok\s.*\s# skip/) { $css = 'skipped' }
+ elsif ($line =~ /^ok\s/) { $css = 'passed' }
+ elsif ($line =~ /^not\s+ok\s/) { $css = 'failed' }
elsif ($line =~ /^#\s#\|\s/) { $css = 'log-esm' }
elsif ($line =~ /^#\s/) { $css = 'log' }
@@ -239,6 +243,8 @@ END_HTML
$line = sprintf("<a name='t--%05d'>%s</a>", $2, $line);
}
$line = "<span class='bats-$css'>$line</span>";
+
+ $bats_count{$css}++;
}
print { $out_fh } "<span class=\"timestamp\">$timestamp</span>"
@@ -354,7 +360,30 @@ END_HTML
my $have_formatted_log; # Set on success
if ($out_fh) {
- print { $out_fh } "</pre>\n";
+ # Summary line for BATS tests
+ if (keys %bats_count) {
+ print { $out_fh } "<hr/><span class='bats-summary'>Summary:";
+ my $total = 0;
+ my $comma = '';
+ for my $class (qw(passed failed skipped)) {
+ if (my $n = $bats_count{$class}) {
+ printf { $out_fh } "%s <span class='bats-%s'>%d %s</span>",
+ $comma, $class, $n, ucfirst($class);
+ $total += $n;
+ $comma = ',';
+ }
+ }
+
+ printf { $out_fh } ". Total tests: $total";
+ if (my $expected_total = $bats_count{expected_total}) {
+ if ($total != $expected_total) {
+ print { $out_fh } " <span class='bats-failed'>(WARNING: expected $expected_total)</span>";
+ }
+ }
+ print { $out_fh } "</span>\n";
+ }
+
+ print { $out_fh } "</pre> <!-- end processed output -->\n";
# Did we find a cirrus task? Link back.
if ($cirrus_task) {
diff --git a/contrib/cirrus/logformatter.t b/contrib/cirrus/logformatter.t
new file mode 100755
index 000000000..79c4563c2
--- /dev/null
+++ b/contrib/cirrus/logformatter.t
@@ -0,0 +1,189 @@
+#!/usr/bin/perl
+#
+# tests for logformatter
+#
+(our $ME = $0) =~ s|^.*/||;
+
+use v5.14;
+use strict;
+use warnings;
+
+use FindBin;
+use File::Temp qw(tempdir);
+use Test::More;
+
+#
+# Read the test cases (see __END__ section below)
+#
+my @tests;
+my $context = '';
+while (my $line = <DATA>) {
+ chomp $line;
+
+ if ($line =~ /^==\s+(.*)/) {
+ push @tests, { name => $1, input => [], expect => [] };
+ $context = '';
+ }
+ elsif ($line =~ /^<<</) {
+ $context = 'input';
+ }
+ elsif ($line =~ /^>>>/) {
+ $context = 'expect';
+ }
+ elsif (@tests && $line) {
+ push @{ $tests[-1]{$context} }, $line;
+ }
+}
+
+plan tests => scalar(@tests);
+
+my $tempdir = tempdir("logformatter-test.XXXXXX", TMPDIR => 1, CLEANUP => !$ENV{DEBUG});
+
+chdir $tempdir
+ or die "$ME: Could not cd $tempdir: $!\n";
+
+for my $t (@tests) {
+ my $name = $t->{name};
+ (my $fname = $name) =~ s/\s+/_/g;
+
+ open my $fh_out, '>', "$fname.txt"
+ or die "$ME: Cannot create $tempdir/$fname.txt: $!\n";
+ print { $fh_out } "$_\n" for @{$t->{input}};
+ close $fh_out
+ or die "$ME: Error writing $tempdir/$fname.txt: $!\n";
+
+ system("$FindBin::Bin/logformatter $fname <$fname.txt >/dev/null");
+ open my $fh_in, '<', "$fname.log.html"
+ or die "$ME: Fatal: $fname: logformatter did not create .log.html\n";
+ my @actual;
+ while (my $line = <$fh_in>) {
+ chomp $line;
+ push @actual, $line if $line =~ / begin processed output / .. $line =~ / end processed output /;
+ }
+ close $fh_in;
+
+ # Strip off leading and trailing "<pre>"
+ shift @actual; pop @actual;
+
+ # For debugging: preserve expected results
+ if ($ENV{DEBUG}) {
+ open my $fh_out, '>', "$fname.expect";
+ print { $fh_out } "$_\n" for @{$t->{expect}};
+ close $fh_out;
+ }
+
+ is_deeply \@actual, $t->{expect}, $name;
+}
+
+chdir '/';
+
+
+
+__END__
+
+== simple bats
+
+<<<
+1..4
+ok 1 hi
+ok 2 bye # skip no reason
+not ok 3 fail
+ok 4 blah
+>>>
+1..4
+<span class='bats-passed'><a name='t--00001'>ok 1 hi</a></span>
+<span class='bats-skipped'><a name='t--00002'>ok 2 bye # skip no reason</a></span>
+<span class='bats-failed'><a name='t--00003'>not ok 3 fail</a></span>
+<span class='bats-passed'><a name='t--00004'>ok 4 blah</a></span>
+<hr/><span class='bats-summary'>Summary: <span class='bats-passed'>2 Passed</span>, <span class='bats-failed'>1 Failed</span>, <span class='bats-skipped'>1 Skipped</span>. Total tests: 4</span>
+
+
+
+
+
+
+
+== simple ginkgo
+
+<<<
+$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}
+[08:26:19] START - All [+xxxx] lines that follow are relative to right now.
+[+0002s] GO111MODULE=on go build -mod=vendor -gcflags 'all=-trimpath=/var/tmp/go/src/github.com/containers/libpod' -asmflags 'all=-trimpath=/var/tmp/go/src/github.com/containers/libpod' -ldflags '-X github.com/containers/libpod/libpod/define.gitCommit=40f5d8b1becd381c4e8283ed3940d09193e4fe06 -X github.com/containers/libpod/libpod/define.buildInfo=1582809981 -X github.com/containers/libpod/libpod/config._installPrefix=/usr/local -X github.com/containers/libpod/libpod/config._etcDir=/etc -extldflags ""' -tags " selinux systemd exclude_graphdriver_devicemapper seccomp varlink" -o bin/podman github.com/containers/libpod/cmd/podman
+[+0103s] •
+[+0103s] ------------------------------
+[+0103s] Podman pod restart
+[+0103s] podman pod restart single empty pod
+[+0103s] /var/tmp/go/src/github.com/containers/libpod/test/e2e/pod_restart_test.go:41
+[+0103s] [BeforeEach] Podman pod restart
+[+0103s] /var/tmp/go/src/github.com/containers/libpod/test/e2e/pod_restart_test.go:18
+[+0103s] [It] podman pod restart single empty pod
+[+0103s] /var/tmp/go/src/github.com/containers/libpod/test/e2e/pod_restart_test.go:41
+[+0103s] Running: /var/tmp/go/src/github.com/containers/libpod/bin/podman --storage-opt vfs.imagestore=/tmp/podman/imagecachedir --root /tmp/podman_test553496330/crio --runroot /tmp/podman_test553496330/crio-run --runtime /usr/bin/runc --conmon /usr/bin/conmon --cni-config-dir /etc/cni/net.d --cgroup-manager systemd --tmpdir /tmp/podman_test553496330 --events-backend file --storage-driver vfs pod create --infra=false --share
+[+0103s] 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+[+0103s] output: 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+[+0103s] Running: /var/tmp/go/src/github.com/containers/libpod/bin/podman --storage-opt vfs.imagestore=/tmp/podman/imagecachedir --root /tmp/podman_test553496330/crio --runroot /tmp/podman_test553496330/crio-run --runtime /usr/bin/runc --conmon /usr/bin/conmon --cni-config-dir /etc/cni/net.d --cgroup-manager systemd --tmpdir /tmp/podman_test553496330 --events-backend file --storage-driver vfs pod restart 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+[+0103s] Error: no containers in pod 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89 have no dependencies, cannot start pod: no such container
+[+0103s] output:
+[+0103s] [AfterEach] Podman pod restart
+[+0103s] /var/tmp/go/src/github.com/containers/libpod/test/e2e/pod_restart_test.go:28
+[+0103s] Running: /var/tmp/go/src/github.com/containers/libpod/bin/podman --storage-opt vfs.imagestore=/tmp/podman/imagecachedir --root /tmp/podman_test553496330/crio --runroot /tmp/podman_test553496330/crio-run --runtime /usr/bin/runc --conmon /usr/bin/conmon --cni-config-dir /etc/cni/net.d --cgroup-manager systemd --tmpdir /tmp/podman_test553496330 --events-backend file --storage-driver vfs pod rm -fa
+[+0103s] 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+[+0107s] •
+[+0107s] ------------------------------
+[+0107s] podman system reset
+>>>
+$SCRIPT_BASE/integration_test.sh |&amp; ${TIMESTAMP}
+[08:26:19] START - All [+xxxx] lines that follow are relative to right now.
+<span class="timestamp">[+0002s] </span>GO111MODULE=on go build -mod=vendor -gcflags &#39;all=-trimpath=/var/tmp/go/src/github.com/containers/libpod&#39; -asmflags &#39;all=-trimpath=/var/tmp/go/src/github.com/containers/libpod&#39; -ldflags &#39;-X github.com/containers/libpod/libpod/define.gitCommit=40f5d8b1becd381c4e8283ed3940d09193e4fe06 -X github.com/containers/libpod/libpod/define.buildInfo=1582809981 -X github.com/containers/libpod/libpod/config._installPrefix=/usr/local -X github.com/containers/libpod/libpod/config._etcDir=/etc -extldflags &quot;&quot;&#39; -tags &quot; selinux systemd exclude_graphdriver_devicemapper seccomp varlink&quot; -o bin/podman github.com/containers/libpod/cmd/podman
+<span class="timestamp">[+0103s] </span>•
+</pre>
+<hr />
+<pre>
+<span class="timestamp">[+0103s] </span>Podman pod restart
+<span class="timestamp"> </span><a name='t--podman-pod-restart-single-empty-pod--1'><h2> podman pod restart single empty pod</h2></a>
+<span class="timestamp"> </span> /var/tmp/go/src/github.com<a class="codelink" href='https://github.com/containers/libpod/blob/40f5d8b1becd381c4e8283ed3940d09193e4fe06/test/e2e/pod_restart_test.go#L41'>/containers/libpod/test/e2e/pod_restart_test.go:41</a>
+<span class="timestamp"> </span>[BeforeEach] Podman pod restart
+<span class="timestamp"> </span> /var/tmp/go/src/github.com<a class="codelink" href='https://github.com/containers/libpod/blob/40f5d8b1becd381c4e8283ed3940d09193e4fe06/test/e2e/pod_restart_test.go#L18'>/containers/libpod/test/e2e/pod_restart_test.go:18</a>
+<span class="timestamp"> </span>[It] podman pod restart single empty pod
+<span class="timestamp"> </span> /var/tmp/go/src/github.com<a class="codelink" href='https://github.com/containers/libpod/blob/40f5d8b1becd381c4e8283ed3940d09193e4fe06/test/e2e/pod_restart_test.go#L41'>/containers/libpod/test/e2e/pod_restart_test.go:41</a>
+<span class="timestamp"> </span>Running: <span title="/var/tmp/go/src/github.com/containers/libpod/bin/podman"><b>podman</b></span> <span class="boring" title="--storage-opt vfs.imagestore=/tmp/podman/imagecachedir
+--root /tmp/podman_test553496330/crio
+--runroot /tmp/podman_test553496330/crio-run
+--runtime /usr/bin/runc
+--conmon /usr/bin/conmon
+--cni-config-dir /etc/cni/net.d
+--cgroup-manager systemd
+--tmpdir /tmp/podman_test553496330
+--events-backend file
+--storage-driver vfs">[options]</span><b> pod create --infra=false --share</b>
+<span class="timestamp"> </span>4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+<span class="timestamp"> </span>Running: <span title="/var/tmp/go/src/github.com/containers/libpod/bin/podman"><b>podman</b></span> <span class="boring" title="--storage-opt vfs.imagestore=/tmp/podman/imagecachedir
+--root /tmp/podman_test553496330/crio
+--runroot /tmp/podman_test553496330/crio-run
+--runtime /usr/bin/runc
+--conmon /usr/bin/conmon
+--cni-config-dir /etc/cni/net.d
+--cgroup-manager systemd
+--tmpdir /tmp/podman_test553496330
+--events-backend file
+--storage-driver vfs">[options]</span><b> pod restart 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89</b>
+<span class="timestamp"> </span><span class='log-warn'>Error: no containers in pod 4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89 have no dependencies, cannot start pod: no such container</span>
+<span class="timestamp"> </span>output:
+<span class="timestamp"> </span>[AfterEach] Podman pod restart
+<span class="timestamp"> </span> /var/tmp/go/src/github.com<a class="codelink" href='https://github.com/containers/libpod/blob/40f5d8b1becd381c4e8283ed3940d09193e4fe06/test/e2e/pod_restart_test.go#L28'>/containers/libpod/test/e2e/pod_restart_test.go:28</a>
+<span class="timestamp"> </span>Running: <span title="/var/tmp/go/src/github.com/containers/libpod/bin/podman"><b>podman</b></span> <span class="boring" title="--storage-opt vfs.imagestore=/tmp/podman/imagecachedir
+--root /tmp/podman_test553496330/crio
+--runroot /tmp/podman_test553496330/crio-run
+--runtime /usr/bin/runc
+--conmon /usr/bin/conmon
+--cni-config-dir /etc/cni/net.d
+--cgroup-manager systemd
+--tmpdir /tmp/podman_test553496330
+--events-backend file
+--storage-driver vfs">[options]</span><b> pod rm -fa</b>
+<span class="timestamp"> </span>4810be0cfbd42241e349dbe7d50fbc54405cd320a6637c65fd5323f34d64af89
+<span class="timestamp">[+0107s] </span>•
+</pre>
+<hr />
+<pre>
+<span class="timestamp">[+0107s] </span>podman system reset
diff --git a/contrib/cirrus/packer/fedora_packaging.sh b/contrib/cirrus/packer/fedora_packaging.sh
new file mode 100644
index 000000000..e80d48bc8
--- /dev/null
+++ b/contrib/cirrus/packer/fedora_packaging.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# This script is called from fedora_setup.sh and various Dockerfiles.
+# It's not intended to be used outside of those contexts. It assumes the lib.sh
+# library has already been sourced, and that all "ground-up" package-related activity
+# needs to be done, including repository setup and initial update.
+
+set -e
+
+echo "Updating/Installing repos and packages for $OS_REL_VER"
+
+source $GOSRC/$SCRIPT_BASE/lib.sh
+
+# Pre-req. to install automation tooing
+$LILTO $SUDO dnf install -y git
+
+# Install common automation tooling (i.e. ooe.sh)
+curl --silent --show-error --location \
+ --url "https://raw.githubusercontent.com/containers/automation/master/bin/install_automation.sh" | \
+ $SUDO env INSTALL_PREFIX=/usr/share /bin/bash -s - "$INSTALL_AUTOMATION_VERSION"
+# Reload installed environment right now (happens automatically in a new process)
+source /usr/share/automation/environment
+
+# Set this to 1 to NOT enable updates-testing repository
+DISABLE_UPDATES_TESTING=${DISABLE_UPDATES_TESTING:0}
+
+# Do not enable update-stesting on the previous Fedora release
+if ((DISABLE_UPDATES_TESTING!=0)); then
+ warn "Enabling updates-testing repository for image based on $FEDORA_BASE_IMAGE"
+ $LILTO $SUDO ooe.sh dnf install -y 'dnf-command(config-manager)'
+ $LILTO $SUDO ooe.sh dnf config-manager --set-enabled updates-testing
+else
+ warn "NOT enabling updates-testing repository for image based on $PRIOR_FEDORA_BASE_IMAGE"
+fi
+
+$BIGTO ooe.sh $SUDO dnf update -y
+
+REMOVE_PACKAGES=()
+INSTALL_PACKAGES=(\
+ autoconf
+ automake
+ bash-completion
+ bats
+ bridge-utils
+ btrfs-progs-devel
+ buildah
+ bzip2
+ conmon
+ container-selinux
+ containernetworking-plugins
+ containers-common
+ criu
+ device-mapper-devel
+ dnsmasq
+ emacs-nox
+ file
+ findutils
+ fuse3
+ fuse3-devel
+ gcc
+ git
+ glib2-devel
+ glibc-static
+ gnupg
+ go-md2man
+ golang
+ gpgme-devel
+ iproute
+ iptables
+ jq
+ libassuan-devel
+ libcap-devel
+ libmsi1
+ libnet
+ libnet-devel
+ libnl3-devel
+ libseccomp
+ libseccomp-devel
+ libselinux-devel
+ libtool
+ libvarlink-util
+ lsof
+ make
+ msitools
+ nmap-ncat
+ ostree-devel
+ pandoc
+ podman
+ procps-ng
+ protobuf
+ protobuf-c
+ protobuf-c-devel
+ protobuf-devel
+ python
+ python3-dateutil
+ python3-psutil
+ python3-pytoml
+ rsync
+ selinux-policy-devel
+ skopeo
+ skopeo-containers
+ slirp4netns
+ unzip
+ vim
+ wget
+ which
+ xz
+ zip
+)
+
+case "$OS_RELEASE_VER" in
+ 30)
+ INSTALL_PACKAGES+=(\
+ atomic-registries
+ golang-github-cpuguy83-go-md2man
+ python2-future
+ runc
+ )
+ REMOVE_PACKAGES+=(crun)
+ ;;
+ 31)
+ INSTALL_PACKAGES+=(crun)
+ REMOVE_PACKAGES+=(runc)
+ ;;
+ 32)
+ INSTALL_PACKAGES+=(crun)
+ REMOVE_PACKAGES+=(runc)
+ ;;
+ *)
+ bad_os_id_ver ;;
+esac
+
+echo "Installing general build/test dependencies for Fedora '$OS_RELEASE_VER'"
+$BIGTO ooe.sh $SUDO dnf install -y ${INSTALL_PACKAGES[@]}
+
+[[ ${#REMOVE_PACKAGES[@]} -eq 0 ]] || \
+ $LILTO ooe.sh $SUDO dnf erase -y ${REMOVE_PACKAGES[@]}
+
+export GOPATH="$(mktemp -d)"
+trap "$SUDO rm -rf $GOPATH" EXIT
+ooe.sh $SUDO $GOSRC/hack/install_catatonit.sh
diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh
index 81a46b13f..3830b3bc4 100644
--- a/contrib/cirrus/packer/fedora_setup.sh
+++ b/contrib/cirrus/packer/fedora_setup.sh
@@ -6,130 +6,26 @@
set -e
# Load in library (copied by packer, before this script was run)
-source /tmp/libpod/$SCRIPT_BASE/lib.sh
+source $GOSRC/$SCRIPT_BASE/lib.sh
-req_env_var SCRIPT_BASE PACKER_BUILDER_NAME GOSRC FEDORA_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER
+req_env_var SCRIPT_BASE PACKER_BASE INSTALL_AUTOMATION_VERSION PACKER_BUILDER_NAME GOSRC FEDORA_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER
-install_ooe
-
-export GOPATH="$(mktemp -d)"
-trap "sudo rm -rf $GOPATH" EXIT
-
-$BIGTO ooe.sh sudo dnf update -y
+workaround_bfq_bug
# Do not enable update-stesting on the previous Fedora release
if [[ "$FEDORA_BASE_IMAGE" =~ "${OS_RELEASE_ID}-cloud-base-${OS_RELEASE_VER}" ]]; then
- warn "Enabling updates-testing repository for image based on $FEDORA_BASE_IMAGE"
- $LILTO ooe.sh sudo dnf install -y 'dnf-command(config-manager)'
- $LILTO ooe.sh sudo dnf config-manager --set-enabled updates-testing
+ DISABLE_UPDATES_TESTING=0
else
- warn "NOT enabling updates-testing repository for image based on $PRIOR_FEDORA_BASE_IMAGE"
+ DISABLE_UPDATES_TESTING=1
fi
-REMOVE_PACKAGES=()
-INSTALL_PACKAGES=(\
- autoconf
- automake
- bash-completion
- bats
- bridge-utils
- btrfs-progs-devel
- bzip2
- conmon
- container-selinux
- containernetworking-plugins
- containers-common
- criu
- device-mapper-devel
- dnsmasq
- emacs-nox
- file
- findutils
- fuse3
- fuse3-devel
- gcc
- git
- glib2-devel
- glibc-static
- gnupg
- go-md2man
- golang
- gpgme-devel
- iproute
- iptables
- jq
- libassuan-devel
- libcap-devel
- libmsi1
- libnet
- libnet-devel
- libnl3-devel
- libseccomp
- libseccomp-devel
- libselinux-devel
- libtool
- libvarlink-util
- lsof
- make
- msitools
- nmap-ncat
- ostree-devel
- pandoc
- podman
- procps-ng
- protobuf
- protobuf-c
- protobuf-c-devel
- protobuf-devel
- protobuf-python
- python
- python3-dateutil
- python3-psutil
- python3-pytoml
- rsync
- runc
- selinux-policy-devel
- skopeo
- skopeo-containers
- slirp4netns
- unzip
- vim
- wget
- which
- xz
- zip
-)
-case "$OS_RELEASE_VER" in
- 30)
- INSTALL_PACKAGES+=(\
- atomic-registries
- golang-github-cpuguy83-go-md2man
- python2-future
- runc
- )
- REMOVE_PACKAGES+=(crun)
- ;;
- 31)
- INSTALL_PACKAGES+=(crun)
- REMOVE_PACKAGES+=(runc)
- ;;
- *)
- bad_os_id_ver ;;
-esac
-
-echo "Installing general build/test dependencies for Fedora '$OS_RELEASE_VER'"
-$BIGTO ooe.sh sudo dnf install -y ${INSTALL_PACKAGES[@]}
-
-install_buildah_packages
-
-[[ "${#REMOVE_PACKAGES[@]}" -eq "0" ]] || \
- $LILTO ooe.sh sudo dnf erase -y ${REMOVE_PACKAGES[@]}
+bash $PACKER_BASE/fedora_packaging.sh
+# Load installed environment right now (happens automatically in a new process)
+source /usr/share/automation/environment
echo "Enabling cgroup management from containers"
ooe.sh sudo setsebool container_manage_cgroup true
-ooe.sh sudo /tmp/libpod/hack/install_catatonit.sh
-
# Ensure there are no disruptive periodic services enabled by default in image
systemd_banish
diff --git a/contrib/cirrus/packer/libpod_base_images.yml b/contrib/cirrus/packer/libpod_base_images.yml
index 255723d57..a66fac31c 100644
--- a/contrib/cirrus/packer/libpod_base_images.yml
+++ b/contrib/cirrus/packer/libpod_base_images.yml
@@ -17,14 +17,14 @@ variables:
PRIOR_UBUNTU_BASE_IMAGE:
# Latest Fedora release
- FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2"
- FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-31-1.9-x86_64-CHECKSUM"
- FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-31-1-9'
+ FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-Base-32-20200406.n.0.x86_64.qcow2"
+ FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/development/32/Cloud/x86_64/images/Fedora-Cloud-32-x86_64-20200406.n.0-CHECKSUM"
+ FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-32-n-0'
# Prior Fedora release
- PRIOR_FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/30/Cloud/x86_64/images/Fedora-Cloud-Base-30-1.2.x86_64.qcow2"
- PRIOR_FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/30/Cloud/x86_64/images/Fedora-Cloud-30-1.2-x86_64-CHECKSUM"
- PRIOR_FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-30-1-2'
+ PRIOR_FEDORA_IMAGE_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2"
+ PRIOR_FEDORA_CSUM_URL: "https://dl.fedoraproject.org/pub/fedora/linux/releases/31/Cloud/x86_64/images/Fedora-Cloud-31-1.9-x86_64-CHECKSUM"
+ PRIOR_FEDORA_BASE_IMAGE_NAME: 'fedora-cloud-base-31-1-9'
# The name of the image in GCE used for packer build libpod_images.yml
IBI_BASE_NAME: 'image-builder-image'
diff --git a/contrib/cirrus/packer/libpod_images.yml b/contrib/cirrus/packer/libpod_images.yml
index 074a813af..e33ad775e 100644
--- a/contrib/cirrus/packer/libpod_images.yml
+++ b/contrib/cirrus/packer/libpod_images.yml
@@ -51,12 +51,12 @@ builders:
source_image_family: 'prior-ubuntu-base'
- <<: *gce_hosted_image
- name: 'fedora-31'
+ name: 'fedora-32'
source_image: '{{user `FEDORA_BASE_IMAGE`}}'
source_image_family: 'fedora-base'
- <<: *gce_hosted_image
- name: 'fedora-30'
+ name: 'fedora-31'
source_image: '{{user `PRIOR_FEDORA_BASE_IMAGE`}}'
source_image_family: 'prior-fedora-base'
@@ -71,6 +71,7 @@ provisioners:
environment_vars:
- 'PACKER_BUILDER_NAME={{build_name}}'
- 'GOSRC=/tmp/libpod'
+ - 'PACKER_BASE={{user `PACKER_BASE`}}'
- 'SCRIPT_BASE={{user `SCRIPT_BASE`}}'
post-processors:
diff --git a/contrib/cirrus/packer/ubuntu_packaging.sh b/contrib/cirrus/packer/ubuntu_packaging.sh
new file mode 100644
index 000000000..b57bc95e9
--- /dev/null
+++ b/contrib/cirrus/packer/ubuntu_packaging.sh
@@ -0,0 +1,168 @@
+#!/bin/bash
+
+# This script is called from ubuntu_setup.sh and various Dockerfiles.
+# It's not intended to be used outside of those contexts. It assumes the lib.sh
+# library has already been sourced, and that all "ground-up" package-related activity
+# needs to be done, including repository setup and initial update.
+
+set -e
+
+echo "Updating/Installing repos and packages for $OS_REL_VER"
+
+source $GOSRC/$SCRIPT_BASE/lib.sh
+
+echo "Updating/configuring package repositories."
+$BIGTO $SUDOAPTGET update
+
+echo "Installing deps to add third-party repositories and automation tooling"
+$LILTO $SUDOAPTGET install software-properties-common git curl
+
+# Install common automation tooling (i.e. ooe.sh)
+curl --silent --show-error --location \
+ --url "https://raw.githubusercontent.com/containers/automation/master/bin/install_automation.sh" | \
+ $SUDO env INSTALL_PREFIX=/usr/share /bin/bash -s - "$INSTALL_AUTOMATION_VERSION"
+# Reload installed environment right now (happens automatically in a new process)
+source /usr/share/automation/environment
+
+$LILTO ooe.sh $SUDOAPTADD ppa:criu/ppa
+
+# Install newer version of golang
+if [[ "$OS_RELEASE_VER" -eq "18" ]]
+then
+ $LILTO ooe.sh $SUDOAPTADD ppa:longsleep/golang-backports
+fi
+
+echo "Configuring/Instaling deps from Open build server"
+VERSION_ID=$(source /etc/os-release; echo $VERSION_ID)
+echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$VERSION_ID/ /" \
+ | ooe.sh $SUDO tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
+ooe.sh curl -L -o /tmp/Release.key "https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key"
+ooe.sh $SUDO apt-key add - < /tmp/Release.key
+
+INSTALL_PACKAGES=(\
+ apparmor
+ aufs-tools
+ autoconf
+ automake
+ bash-completion
+ bison
+ build-essential
+ buildah
+ bzip2
+ conmon
+ containernetworking-plugins
+ containers-common
+ coreutils
+ cri-o-runc
+ criu
+ curl
+ dnsmasq
+ e2fslibs-dev
+ emacs-nox
+ file
+ gawk
+ gcc
+ gettext
+ git
+ go-md2man
+ golang
+ iproute2
+ iptables
+ jq
+ libaio-dev
+ libapparmor-dev
+ libcap-dev
+ libdevmapper-dev
+ libdevmapper1.02.1
+ libfuse-dev
+ libfuse2
+ libglib2.0-dev
+ libgpgme11-dev
+ liblzma-dev
+ libnet1
+ libnet1-dev
+ libnl-3-dev
+ libprotobuf-c-dev
+ libprotobuf-dev
+ libseccomp-dev
+ libseccomp2
+ libselinux-dev
+ libsystemd-dev
+ libtool
+ libudev-dev
+ libvarlink
+ lsof
+ make
+ netcat
+ openssl
+ pkg-config
+ podman
+ protobuf-c-compiler
+ protobuf-compiler
+ python-future
+ python-minimal
+ python-protobuf
+ python3-dateutil
+ python3-pip
+ python3-psutil
+ python3-pytoml
+ python3-setuptools
+ rsync
+ runc
+ scons
+ skopeo
+ slirp4netns
+ socat
+ sudo
+ unzip
+ vim
+ wget
+ xz-utils
+ yum-utils
+ zip
+ zlib1g-dev
+)
+
+if [[ $OS_RELEASE_VER -ge 19 ]]
+then
+ INSTALL_PACKAGES+=(\
+ bats
+ btrfs-progs
+ fuse3
+ libbtrfs-dev
+ libfuse3-dev
+ )
+else
+ echo "Downloading version of bats with fix for a \$IFS related bug in 'run' command"
+ cd /tmp
+ BATS_URL='http://launchpadlibrarian.net/438140887/bats_1.1.0+git104-g1c83a1b-1_all.deb'
+ curl -L -O "$BATS_URL"
+ cd -
+ INSTALL_PACKAGES+=(\
+ /tmp/$(basename $BATS_URL)
+ btrfs-tools
+ )
+fi
+
+# Do this at the last possible moment to avoid dpkg lock conflicts
+echo "Upgrading all packages"
+$BIGTO ooe.sh $SUDOAPTGET upgrade
+
+echo "Installing general testing and system dependencies"
+# Necessary to update cache of newly added repos
+$LILTO ooe.sh $SUDOAPTGET update
+$BIGTO ooe.sh $SUDOAPTGET install ${INSTALL_PACKAGES[@]}
+
+export GOPATH="$(mktemp -d)"
+trap "$SUDO rm -rf $GOPATH" EXIT
+echo "Installing cataonit and libseccomp.sudo"
+cd $GOSRC
+ooe.sh $SUDO hack/install_catatonit.sh
+ooe.sh $SUDO make install.libseccomp.sudo
+
+CRIO_RUNC_PATH="/usr/lib/cri-o-runc/sbin/runc"
+if $SUDO dpkg -L cri-o-runc | grep -m 1 -q "$CRIO_RUNC_PATH"
+then
+ echo "Linking $CRIO_RUNC_PATH to /usr/bin/runc for ease of testing."
+ $SUDO ln -f "$CRIO_RUNC_PATH" "/usr/bin/runc"
+fi
diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh
index 46e7a620f..2febbd265 100644
--- a/contrib/cirrus/packer/ubuntu_setup.sh
+++ b/contrib/cirrus/packer/ubuntu_setup.sh
@@ -8,171 +8,27 @@ set -e
# Load in library (copied by packer, before this script was run)
source $GOSRC/$SCRIPT_BASE/lib.sh
-req_env_var SCRIPT_BASE
+req_env_var SCRIPT_BASE PACKER_BASE INSTALL_AUTOMATION_VERSION PACKER_BUILDER_NAME GOSRC UBUNTU_BASE_IMAGE OS_RELEASE_ID OS_RELEASE_VER
-install_ooe
-
-export GOPATH="$(mktemp -d)"
-trap "sudo rm -rf $GOPATH" EXIT
+# Ensure there are no disruptive periodic services enabled by default in image
+systemd_banish
# Stop disruption upon boot ASAP after booting
echo "Disabling all packaging activity on boot"
-# Don't let sed process sed's temporary files
-_FILEPATHS=$(sudo ls -1 /etc/apt/apt.conf.d)
-for filename in $_FILEPATHS; do \
+for filename in $(sudo ls -1 /etc/apt/apt.conf.d); do \
echo "Checking/Patching $filename"
sudo sed -i -r -e "s/$PERIODIC_APT_RE/"'\10"\;/' "/etc/apt/apt.conf.d/$filename"; done
-echo "Updating/configuring package repositories."
-$BIGTO $SUDOAPTGET update
-
-echo "Upgrading all packages"
-$BIGTO $SUDOAPTGET upgrade
-
-echo "Adding third-party repositories and PPAs"
-$LILTO $SUDOAPTGET install software-properties-common
-$LILTO $SUDOAPTADD ppa:criu/ppa
-if [[ "$OS_RELEASE_VER" -eq "18" ]]
-then
- $LILTO $SUDOAPTADD ppa:longsleep/golang-backports
-fi
-
-echo "Configuring/Instaling deps from Open build server"
-VERSION_ID=$(source /etc/os-release; echo $VERSION_ID)
-echo "deb http://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_$VERSION_ID/ /" \
- | ooe.sh sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
-ooe.sh curl -L -o /tmp/Release.key "https://download.opensuse.org/repositories/devel:kubic:libcontainers:stable/xUbuntu_${VERSION_ID}/Release.key"
-ooe.sh sudo apt-key add - < /tmp/Release.key
-
-INSTALL_PACKAGES=(\
- apparmor
- aufs-tools
- autoconf
- automake
- bash-completion
- bison
- build-essential
- bzip2
- conmon
- containernetworking-plugins
- containers-common
- coreutils
- cri-o-runc
- criu
- curl
- dnsmasq
- e2fslibs-dev
- emacs-nox
- file
- gawk
- gcc
- gettext
- git
- go-md2man
- golang
- iproute2
- iptables
- jq
- libaio-dev
- libapparmor-dev
- libcap-dev
- libdevmapper-dev
- libdevmapper1.02.1
- libfuse-dev
- libfuse2
- libglib2.0-dev
- libgpgme11-dev
- liblzma-dev
- libnet1
- libnet1-dev
- libnl-3-dev
- libprotobuf-c-dev
- libprotobuf-dev
- libseccomp-dev
- libseccomp2
- libselinux-dev
- libsystemd-dev
- libtool
- libudev-dev
- libvarlink
- lsof
- make
- netcat
- openssl
- pkg-config
- podman
- protobuf-c-compiler
- protobuf-compiler
- python-future
- python-minimal
- python-protobuf
- python3-dateutil
- python3-pip
- python3-psutil
- python3-pytoml
- python3-setuptools
- rsync
- runc
- scons
- skopeo
- slirp4netns
- socat
- unzip
- vim
- wget
- xz-utils
- yum-utils
- zip
- zlib1g-dev
-)
+bash $PACKER_BASE/ubuntu_packaging.sh
-if [[ "$OS_RELEASE_VER" -ge "19" ]]
-then
- INSTALL_PACKAGES+=(\
- bats
- btrfs-progs
- fuse3
- libbtrfs-dev
- libfuse3-dev
- )
-else
- echo "Downloading version of bats with fix for a \$IFS related bug in 'run' command"
- cd /tmp
- BATS_URL='http://launchpadlibrarian.net/438140887/bats_1.1.0+git104-g1c83a1b-1_all.deb'
- curl -L -O "$BATS_URL"
- cd -
- INSTALL_PACKAGES+=(\
- /tmp/$(basename $BATS_URL)
- btrfs-tools
- )
-
- echo "Forced Ubuntu 18 kernel to enable cgroup swap accounting."
- SEDCMD='s/^GRUB_CMDLINE_LINUX="(.*)"/GRUB_CMDLINE_LINUX="\1 cgroup_enable=memory swapaccount=1"/g'
- ooe.sh sudo sed -re "$SEDCMD" -i /etc/default/grub.d/*
- ooe.sh sudo sed -re "$SEDCMD" -i /etc/default/grub
- ooe.sh sudo update-grub
-fi
-
-echo "Installing general testing and system dependencies"
-# Necessary to update cache of newly added repos
-$LILTO $SUDOAPTGET update
-$BIGTO $SUDOAPTGET install ${INSTALL_PACKAGES[@]}
-
-install_buildah_packages
-
-echo "Installing cataonit and libseccomp.sudo"
-ooe.sh sudo /tmp/libpod/hack/install_catatonit.sh
-ooe.sh sudo make -C /tmp/libpod install.libseccomp.sudo
-
-# Ensure there are no disruptive periodic services enabled by default in image
-systemd_banish
+# Load installed environment right now (happens automatically in a new process)
+source /usr/share/automation/environment
-CRIO_RUNC_PATH="/usr/lib/cri-o-runc/sbin/runc"
-if sudo dpkg -L cri-o-runc | grep -m 1 -q "$CRIO_RUNC_PATH"
-then
- echo "Linking $CRIO_RUNC_PATH to /usr/bin/runc for ease of testing."
- sudo ln -f "$CRIO_RUNC_PATH" "/usr/bin/runc"
-fi
+echo "Making Ubuntu kernel to enable cgroup swap accounting as it is not the default."
+SEDCMD='s/^GRUB_CMDLINE_LINUX="(.*)"/GRUB_CMDLINE_LINUX="\1 cgroup_enable=memory swapaccount=1"/g'
+ooe.sh sudo sed -re "$SEDCMD" -i /etc/default/grub.d/*
+ooe.sh sudo sed -re "$SEDCMD" -i /etc/default/grub
+ooe.sh sudo update-grub
ubuntu_finalize
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
index eceb80b00..6bec9625e 100755
--- a/contrib/cirrus/setup_environment.sh
+++ b/contrib/cirrus/setup_environment.sh
@@ -43,14 +43,8 @@ case "${OS_RELEASE_ID}" in
fedora)
# All SELinux distros need this for systemd-in-a-container
setsebool container_manage_cgroup true
- if [[ "$ADD_SECOND_PARTITION" == "true" ]]; then
- bash "$SCRIPT_BASE/add_second_partition.sh"
- fi
- warn "Switching io scheduler to 'deadline' to avoid RHBZ 1767539"
- warn "aka https://bugzilla.kernel.org/show_bug.cgi?id=205447"
- echo "mq-deadline" > /sys/block/sda/queue/scheduler
- cat /sys/block/sda/queue/scheduler
+ workaround_bfq_bug
if [[ "$ADD_SECOND_PARTITION" == "true" ]]; then
bash "$SCRIPT_BASE/add_second_partition.sh"
diff --git a/contrib/dependencies.txt b/contrib/dependencies.txt
new file mode 100644
index 000000000..5a6fa9834
--- /dev/null
+++ b/contrib/dependencies.txt
@@ -0,0 +1,34 @@
+# Fedora dependencies for building podman
+
+btrfs-progs-devel
+bzip2
+container-selinux
+containernetworking-cni
+device-mapper-devel
+findutils
+git
+glib2-devel
+glibc-static
+golang
+gpgme-devel
+iptables
+libassuan-devel
+libseccomp-devel
+libselinux-devel
+lsof
+make
+nmap-ncat
+procps-ng
+python
+python3-dateutil
+python3-pip
+python3-psutil
+python3-pytoml
+python3-pyyaml
+python3-varlink
+rsync
+slirp4netns
+unzip
+which
+xz
+zip
diff --git a/contrib/gate/Dockerfile b/contrib/gate/Dockerfile
index 4fddae557..f7cd8f2b3 100644
--- a/contrib/gate/Dockerfile
+++ b/contrib/gate/Dockerfile
@@ -1,38 +1,4 @@
FROM fedora:31
-RUN dnf -y install \
- btrfs-progs-devel \
- bzip2 \
- container-selinux \
- containernetworking-cni \
- device-mapper-devel \
- findutils \
- git \
- glib2-devel \
- glibc-static \
- golang \
- gpgme-devel \
- iptables \
- libassuan-devel \
- libseccomp-devel \
- libselinux-devel \
- lsof \
- make \
- nmap-ncat \
- procps-ng \
- python \
- python3-dateutil \
- python3-pip \
- python3-psutil \
- python3-pytoml \
- python3-pyyaml \
- python3-varlink \
- rsync \
- slirp4netns \
- unzip \
- which \
- xz \
- zip \
- && dnf clean all
ENV GOPATH="/var/tmp/go" \
GOBIN="/var/tmp/go/bin" \
@@ -43,6 +9,11 @@ ENV GOPATH="/var/tmp/go" \
# Only needed for installing build-time dependencies, then will be removed
COPY / $GOSRC
+# Install packages from dependencies.txt, ignoring commented lines
+RUN dnf -y install \
+ $(grep "^[^#]" $GOSRC/contrib/dependencies.txt) \
+ && dnf clean all
+
# Install dependencies
RUN set -x && \
mkdir -p "$GOBIN" && \
diff --git a/contrib/gate/README.md b/contrib/gate/README.md
index fe1205dc5..b2bc56023 100644
--- a/contrib/gate/README.md
+++ b/contrib/gate/README.md
@@ -1,6 +1,6 @@
![PODMAN logo](../../logo/podman-logo-source.svg)
-A standard container image for lint-checking and validating changes to the libpod
-repository. The
-[contributors guide contains the documentation for usage.](https://github.com/containers/libpod/blob/master/CONTRIBUTING.md#go-format-and-lint). Note that this container image is also utilized
-in automation, see the file [.cirrus.yml](.cirrus.yml)
+The "gate" image is a standard container image for lint-checking and validating
+changes to the libpod repository. It must be built from the repository root as
+[described in the contibutors guide](https://github.com/containers/libpod/blob/master/CONTRIBUTING.md#go-format-and-lint).
+The image is also used in [CI/CD automation](../../.cirrus.yml).
diff --git a/contrib/podmanimage/stable/Dockerfile b/contrib/podmanimage/stable/Dockerfile
index c0c07d9d2..912d16e1c 100644
--- a/contrib/podmanimage/stable/Dockerfile
+++ b/contrib/podmanimage/stable/Dockerfile
@@ -11,16 +11,12 @@ FROM fedora:latest
# Don't include container-selinux and remove
# directories used by yum that are just taking
# up space.
-RUN useradd build; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.*
+RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.*
-# Adjust storage.conf to enable Fuse storage.
-RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
-RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
+ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/
-# Adjust libpod.conf to write logging to a file
-RUN sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
+# chmod containers.conf and adjust storage.conf to enable Fuse storage.
+RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
+RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
-# Set up environment variables to note that this is
-# not starting with usernamespace and default to
-# isolate the filesystem with chroot.
-ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot
+ENV _CONTAINERS_USERNS_CONFIGURED=""
diff --git a/contrib/podmanimage/stable/containers.conf b/contrib/podmanimage/stable/containers.conf
new file mode 100644
index 000000000..e6b806da3
--- /dev/null
+++ b/contrib/podmanimage/stable/containers.conf
@@ -0,0 +1,11 @@
+[containers]
+netns="host"
+userns="host"
+ipcns="host"
+utsns="host"
+cgroupns="host"
+cgroups="disabled"
+[engine]
+cgroup_manager = "cgroupfs"
+events_logger="file"
+runtime="crun"
diff --git a/contrib/podmanimage/stable/manual/Containerfile b/contrib/podmanimage/stable/manual/Containerfile
index d76d6d9b4..4375ea4f4 100644
--- a/contrib/podmanimage/stable/manual/Containerfile
+++ b/contrib/podmanimage/stable/manual/Containerfile
@@ -26,14 +26,11 @@ FROM fedora:latest
COPY /tmp/podman-1.7.0-3.fc30.x86_64.rpm /tmp
RUN yum -y install /tmp/podman-1.7.0-3.fc30.x86_64.rpm fuse-overlayfs --exclude container-selinux; rm -rf /var/cache /var/log/dnf* /var/log/yum.* /tmp/podman*.rpm
-# Adjust storage.conf to enable Fuse storage.
-RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
+ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/
+
+# chmod containers.conf and adjust storage.conf to enable Fuse storage.
+RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
-# Adjust libpod.conf to write logging to a file
-RUN sed -i 's/events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf; mkdir -p /run/systemd/journal
-# Set up environment variables to note that this is
-# not starting with usernamespace and default to
-# isolate the filesystem with chroot.
-ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot
+ENV _CONTAINERS_USERNS_CONFIGURED=""
diff --git a/contrib/podmanimage/testing/Dockerfile b/contrib/podmanimage/testing/Dockerfile
index a8e7653f6..31265a0ea 100644
--- a/contrib/podmanimage/testing/Dockerfile
+++ b/contrib/podmanimage/testing/Dockerfile
@@ -13,16 +13,12 @@ FROM fedora:latest
# Don't include container-selinux and remove
# directories used by yum that are just taking
# up space.
-RUN useradd build; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux --enablerepo updates-testing; rm -rf /var/cache /var/log/dnf* /var/log/yum.*
+RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install podman fuse-overlayfs --exclude container-selinux --enablerepo updates-testing; rm -rf /var/cache /var/log/dnf* /var/log/yum.*
-# Adjust storage.conf to enable Fuse storage.
-RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
-RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
+ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/
-# Adjust libpod.conf to write logging to a file
-RUN sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf
+# chmod containers.conf and adjust storage.conf to enable Fuse storage.
+RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
+RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
-# Set up environment variables to note that this is
-# not starting with usernamespace and default to
-# isolate the filesystem with chroot.
-ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot
+ENV _CONTAINERS_USERNS_CONFIGURED=""
diff --git a/contrib/podmanimage/upstream/Dockerfile b/contrib/podmanimage/upstream/Dockerfile
index 847097920..541670aa2 100644
--- a/contrib/podmanimage/upstream/Dockerfile
+++ b/contrib/podmanimage/upstream/Dockerfile
@@ -17,7 +17,7 @@ ENV GOPATH=/root/podman
# to the container.
# Finally remove the podman directory and a few other packages
# that are needed for building but not running Podman
-RUN useradd build; yum -y update; yum -y reinstall shadow-utils; yum -y install --exclude container-selinux \
+RUN useradd podman; yum -y update; yum -y reinstall shadow-utils; yum -y install --exclude container-selinux \
--enablerepo=updates-testing \
btrfs-progs-devel \
containernetworking-cni \
@@ -37,7 +37,7 @@ RUN useradd build; yum -y update; yum -y reinstall shadow-utils; yum -y install
libselinux-devel \
make \
pkgconfig \
- runc \
+ crun \
fuse-overlayfs \
fuse3 \
containers-common; \
@@ -59,18 +59,14 @@ RUN useradd build; yum -y update; yum -y reinstall shadow-utils; yum -y install
mkdir -p /etc/cni/net.d; \
curl -qsSL https://raw.githubusercontent.com/containers/libpod/master/cni/87-podman-bridge.conflist | tee /etc/cni/net.d/99-loopback.conf; \
mkdir -p /usr/share/containers; \
- cp $GOPATH/src/github.com/containers/libpod/libpod.conf /usr/share/containers; \
- # Adjust libpod.conf to write logging to a file
- sed -i 's/# events_logger = "journald"/events_logger = "file"/g' /usr/share/containers/libpod.conf; \
rm -rf /root/podman/*; \
yum -y remove git golang go-md2man make; \
yum clean all;
-# Adjust storage.conf to enable Fuse storage.
-RUN sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
+ADD https://raw.githubusercontent.com/containers/libpod/master/contrib/podmanimage/stable/containers.conf /etc/containers/
+
+# chmod containers.conf and adjust storage.conf to enable Fuse storage.
+RUN chmod 644 /etc/containers/containers.conf; sed -i -e 's|^#mount_program|mount_program|g' -e '/additionalimage.*/a "/var/lib/shared",' /etc/containers/storage.conf
RUN mkdir -p /var/lib/shared/overlay-images /var/lib/shared/overlay-layers; touch /var/lib/shared/overlay-images/images.lock; touch /var/lib/shared/overlay-layers/layers.lock
-# Set up environment variables to note that this is
-# not starting with usernamespace and default to
-# isolate the filesystem with chroot.
-ENV _BUILDAH_STARTED_IN_USERNS="" BUILDAH_ISOLATION=chroot
+ENV _CONTAINERS_USERNS_CONFIGURED=""
diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in
index afc50f854..1dfbdf208 100644
--- a/contrib/spec/podman.spec.in
+++ b/contrib/spec/podman.spec.in
@@ -377,12 +377,6 @@ Man pages for the %{name} commands
# untar conmon
tar zxf %{SOURCE1}
-sed -i 's/install.remote: podman-remote/install.remote:/' Makefile
-sed -i 's/install.bin: podman/install.bin:/' Makefile
-%if %{with doc}
-sed -i 's/install.man: docs/install.man:/' Makefile
-%endif
-
%build
mkdir _build
pushd _build
@@ -417,22 +411,15 @@ popd
%install
install -dp %{buildroot}%{_unitdir}
install -dp %{buildroot}%{_usr}/lib/systemd/user
-%if %{with doc}
-PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \
- install.bin \
- install.remote \
- install.man \
- install.cni \
- install.systemd \
- install.completions
-%else
PODMAN_VERSION=%{version} %{__make} PREFIX=%{buildroot}%{_prefix} ETCDIR=%{buildroot}%{_sysconfdir} \
- install.bin \
- install.remote \
+ install.bin-nobuild \
+ install.remote-nobuild \
+%if %{with doc}
+ install.man-nobuild \
+%endif
install.cni \
install.systemd \
install.completions
-%endif
mv pkg/hooks/README.md pkg/hooks/README-hooks.md
diff --git a/docs/source/markdown/podman-image-prune.1.md b/docs/source/markdown/podman-image-prune.1.md
index c76e9bd3f..cf9e50366 100644
--- a/docs/source/markdown/podman-image-prune.1.md
+++ b/docs/source/markdown/podman-image-prune.1.md
@@ -11,6 +11,8 @@ podman-image-prune - Remove all unused images from the local store
you can delete all unused images. Unused images are dangling images as well as any image that
does not have any containers based on it.
+The image prune command does not prune cache images that only use layers that are necessary for other images.
+
## OPTIONS
**--all**, **-a**
diff --git a/docs/source/markdown/podman-manifest-add.1.md b/docs/source/markdown/podman-manifest-add.1.md
new file mode 100644
index 000000000..857a98e12
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-add.1.md
@@ -0,0 +1,76 @@
+% podman-manifest-add(1)
+
+## NAME
+podman\-manifest\-add - Add an image to a manifest list or image index
+
+## SYNOPSIS
+**podman manifest add** *listnameorindexname* *imagename*
+
+## DESCRIPTION
+
+Adds the specified image to the specified manifest list or image index.
+
+## RETURN VALUE
+The list image's ID.
+
+## OPTIONS
+
+**--all**
+
+If the image which should be added to the list or index is itself a list or
+index, add all of the contents to the local list. By default, only one image
+from such a list or index will be added to the list or index. Combining
+*--all* with any of the other options described below is NOT recommended.
+
+**--annotation** *annotation=value*
+
+Set an annotation on the entry for the newly-added image.
+
+**--arch**
+
+Override the architecture which the list or index records as a requirement for
+the image. If *imageName* refers to a manifest list or image index, the
+architecture information will be retrieved from it. Otherwise, it will be
+retrieved from the image's configuration information.
+
+**--features**
+
+Specify the features list which the list or index records as requirements for
+the image. This option is rarely used.
+
+**--os**
+
+Override the OS which the list or index records as a requirement for the image.
+If *imagename* refers to a manifest list or image index, the OS information
+will be retrieved from it. Otherwise, it will be retrieved from the image's
+configuration information.
+
+**--os-version**
+
+Specify the OS version which the list or index records as a requirement for the
+image. This option is rarely used.
+
+**--variant**
+
+Specify the variant which the list or index records for the image. This option
+is typically used to distinguish between multiple entries which share the same
+architecture value, but which expect different versions of its instruction set.
+
+## EXAMPLE
+
+```
+podman manifest add mylist:v1.11 docker://fedora
+71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+```
+podman manifest add --all mylist:v1.11 docker://fedora
+71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+```
+podman manifest add --arch arm64 --variant v8 mylist:v1.11 docker://71c201d10fffdcac52968a000d85a0a016ca1c7d5473948000d3131c1773d965
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-inspect(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest-create.1.md b/docs/source/markdown/podman-manifest-create.1.md
new file mode 100644
index 000000000..941e70c32
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-create.1.md
@@ -0,0 +1,43 @@
+% podman-manifest-create(1)
+
+## NAME
+podman\-manifest\-create - Create a manifest list or image index
+
+## SYNOPSIS
+**podman manifest create** [*options*] *listnameorindexname* [*imagename* ...]
+
+## DESCRIPTION
+
+Creates a new manifest list and stores it as an image in local storage using
+the specified name.
+
+If additional images are specified, they are added to the newly-created list or
+index.
+
+## OPTIONS
+
+**--all**
+
+If any of the images which should be added to the new list or index are
+themselves lists or indexes, add all of their contents. By default, only one
+image from such a list will be added to the newly-created list or index.
+
+## EXAMPLES
+
+```
+podman manifest create mylist:v1.11
+9cfd24048d5fc80903f088f1531a21bff01172abe66effa8941a4c2308dc745f
+```
+
+```
+podman manifest create mylist:v1.11 docker://fedora
+5c2bc76bfb4ba6665a7973f7e1c05ee0536b4580637f27adc9fa5a4b2bc03cf1
+```
+
+```
+podman manifest create --all mylist:v1.11 docker://fedora
+30330571e79c65288a4fca421d9aed29b0210d57294d9c2056743fdcf6e3967b
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-add(1), podman-manifest-inspect(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest-inspect.1.md b/docs/source/markdown/podman-manifest-inspect.1.md
new file mode 100644
index 000000000..efde02643
--- /dev/null
+++ b/docs/source/markdown/podman-manifest-inspect.1.md
@@ -0,0 +1,24 @@
+% podman-manifest-inspect(1)
+
+## NAME
+podman\-manifest\-inspect - Display a manifest list or image index
+
+## SYNOPSIS
+**podman manifest inspect** *listnameorindexname*
+
+## DESCRIPTION
+
+Displays the manifest list or image index stored using the specified image name.
+
+## RETURN VALUE
+
+A formatted JSON representation of the manifest list or image index.
+
+## EXAMPLES
+
+```
+podman manifest inspect mylist:v1.11
+```
+
+## SEE ALSO
+podman(1), podman-manifest(1), podman-manifest-create(1), podman-manifest-add(1), podman-rmi(1)
diff --git a/docs/source/markdown/podman-manifest.1.md b/docs/source/markdown/podman-manifest.1.md
new file mode 100644
index 000000000..70d695883
--- /dev/null
+++ b/docs/source/markdown/podman-manifest.1.md
@@ -0,0 +1,23 @@
+% podman-manifest(1)
+
+## NAME
+podman\-manifest - Create and manipulate manifest lists and image indexes
+
+## SYNOPSIS
+**podman manifest** *subcommand*
+
+## DESCRIPTION
+The `podman manifest` command provides subcommands which can be used to:
+
+ * Create a working Docker manifest list or OCI image index.
+
+## SUBCOMMANDS
+
+| Command | Man Page | Description |
+| ------- | ---------------------------------------------------------- | --------------------------------------------------------------------------- |
+| add | [podman-manifest-add(1)](podman-manifest-add.1.md) | Add an image to a manifest list or image index. |
+| create | [podman-manifest-create(1)](podman-manifest-create.1.md) | Create a manifest list or image index. |
+| inspect | [podman-manifest-inspect(1)](podman-manifest-inspect.1.md) | Display a manifest list or image index. |
+
+## SEE ALSO
+podman(1), podman-manifest-add(1), podman-manifest-create(1), podman-manifest-inspect(1)
diff --git a/docs/source/markdown/podman-pod-stats.1.md b/docs/source/markdown/podman-pod-stats.1.md
index 962edbda0..f70a5a919 100644
--- a/docs/source/markdown/podman-pod-stats.1.md
+++ b/docs/source/markdown/podman-pod-stats.1.md
@@ -7,7 +7,7 @@ podman\-pod\-stats - Display a live stream of resource usage stats for container
**podman pod stats** [*options*] [*pod*]
## DESCRIPTION
-Display a live stream of containers in one or more pods resource usage statistics
+Display a live stream of containers in one or more pods resource usage statistics. Running rootless is only supported on cgroups v2.
## OPTIONS
diff --git a/docs/source/markdown/podman-pull.1.md b/docs/source/markdown/podman-pull.1.md
index b3e35c672..aa558526a 100644
--- a/docs/source/markdown/podman-pull.1.md
+++ b/docs/source/markdown/podman-pull.1.md
@@ -4,9 +4,13 @@
podman\-pull - Pull an image from a registry
## SYNOPSIS
-**podman pull** [*options*] *name*[:*tag*|@*digest*]
+**podman pull** [*options*] *source*
-**podman image pull** [*options*] *name*[:*tag*|@*digest*]
+**podman image pull** [*options*] *source*
+
+**podman pull** [*options*] [*transport*]*name*[:*tag*|@*digest*]
+
+**podman image pull** [*options*] [*transport*]*name*[:*tag*|@*digest*]
## DESCRIPTION
Copies an image from a registry onto the local machine. **podman pull** pulls an
@@ -17,12 +21,12 @@ print the full image ID. **podman pull** can also pull an image
using its digest **podman pull** *image*@*digest*. **podman pull** can be used to pull
images from archives and local storage using different transports.
-## imageID
-Image stored in local container/storage
+## Image storage
+Images are stored in local image storage.
## SOURCE
- The SOURCE is a location to get container images
+ The SOURCE is the location from which the container images are pulled.
The Image "SOURCE" uses a "transport":"details" format.
Multiple transports are supported:
diff --git a/docs/source/markdown/podman-push.1.md b/docs/source/markdown/podman-push.1.md
index 3f0350bcd..f029c8db1 100644
--- a/docs/source/markdown/podman-push.1.md
+++ b/docs/source/markdown/podman-push.1.md
@@ -14,8 +14,8 @@ Push is mainly used to push images to registries, however **podman push**
can be used to save images to tarballs and directories using the following
transports: **dir:**, **docker-archive:**, **docker-daemon:** and **oci-archive:**.
-## imageID
-Image stored in local container/storage
+## Image storage
+Images are pushed from those stored in local image storage.
## DESTINATION
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index cd4148c95..6bac0cc9d 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -169,6 +169,7 @@ the exit codes follow the `chroot` standard, see below:
| [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 one or more containers. |
+| [podman-manifest(1)](podman-manifest.1.md) | Create and manipulate manifest lists and image indexes. |
| [podman-mount(1)](podman-mount.1.md) | Mount a working container's root filesystem. |
| [podman-network(1)](podman-network.1.md) | Manage Podman CNI networks. |
| [podman-pause(1)](podman-pause.1.md) | Pause one or more containers. |
diff --git a/go.mod b/go.mod
index 87d29a26b..14b80e216 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
github.com/containernetworking/cni v0.7.2-0.20200304161608-4fae32b84921
github.com/containernetworking/plugins v0.8.5
github.com/containers/buildah v1.14.8
- github.com/containers/common v0.9.1
+ github.com/containers/common v0.9.5
github.com/containers/conmon v2.0.14+incompatible
github.com/containers/image/v5 v5.4.3
github.com/containers/psgo v1.5.0
@@ -45,7 +45,7 @@ require (
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.9.1
github.com/pmezard/go-difflib v1.0.0
- github.com/rootless-containers/rootlesskit v0.9.3
+ github.com/rootless-containers/rootlesskit v0.9.4
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
github.com/sirupsen/logrus v1.5.0
github.com/spf13/cobra v0.0.7
diff --git a/go.sum b/go.sum
index 75d165b6f..acd345144 100644
--- a/go.sum
+++ b/go.sum
@@ -66,8 +66,8 @@ github.com/containernetworking/plugins v0.8.5/go.mod h1:UZ2539umj8djuRQmBxuazHeJ
github.com/containers/buildah v1.14.8 h1:JbMI0QSOmyZ30Mr2633uCXAj+Fajgh/EFS9xX/Y14oQ=
github.com/containers/buildah v1.14.8/go.mod h1:ytEjHJQnRXC1ygXMyc0FqYkjcoCydqBQkOdxbH563QU=
github.com/containers/common v0.8.1/go.mod h1:VxDJbaA1k6N1TNv9Rt6bQEF4hyKVHNfOfGA5L91ADEs=
-github.com/containers/common v0.9.1 h1:S5lkpnycTI29YzpNJ4RLv49g8sksgYNRNsugPmzQCR8=
-github.com/containers/common v0.9.1/go.mod h1:9YGKPwu6NFYQG2NtSP9bRhNGA8mgd1mUCCkOU2tr+Pc=
+github.com/containers/common v0.9.5 h1:rqGMfYuD1euB38kW2sbQQTRelnrXPQ1E2vkcOP9HNnA=
+github.com/containers/common v0.9.5/go.mod h1:9YGKPwu6NFYQG2NtSP9bRhNGA8mgd1mUCCkOU2tr+Pc=
github.com/containers/conmon v2.0.14+incompatible h1:knU1O1QxXy5YxtjMQVKEyCajROaehizK9FHaICl+P5Y=
github.com/containers/conmon v2.0.14+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.4.3 h1:zn2HR7uu4hpvT5QQHgjqonOzKDuM1I1UHUEmzZT5sbs=
@@ -131,6 +131,7 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e h1:p1yVGRW3nmb85p1
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -372,8 +373,8 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rootless-containers/rootlesskit v0.9.3 h1:hrkZzBZT5vEnhAso6H1jHAcc4DT8h6/hp2z4yL0xu/8=
-github.com/rootless-containers/rootlesskit v0.9.3/go.mod h1:fx5DhInDgnR0Upj+2cOVacKuZJYSNKV5P/bCwGa+quQ=
+github.com/rootless-containers/rootlesskit v0.9.4 h1:6ogX7l3r3nlS7eTB8ePbLSQ6TZR1aVQzRjTy2SIBOzk=
+github.com/rootless-containers/rootlesskit v0.9.4/go.mod h1:fx5DhInDgnR0Upj+2cOVacKuZJYSNKV5P/bCwGa+quQ=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
diff --git a/hack/golangci-lint.sh b/hack/golangci-lint.sh
index 385b21f39..f4e60d8f5 100755
--- a/hack/golangci-lint.sh
+++ b/hack/golangci-lint.sh
@@ -3,13 +3,22 @@
# Need to run linter twice to cover all the build tags code paths
declare -A BUILD_TAGS
+# TODO: add systemd tag
BUILD_TAGS[default]="apparmor,seccomp,selinux"
BUILD_TAGS[abi]="${BUILD_TAGS[default]},ABISupport,varlink,!remoteclient"
-BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,!varlink,remoteclient"
+BUILD_TAGS[tunnel]="${BUILD_TAGS[default]},!ABISupport,varlink,remoteclient"
+
+declare -A SKIP_DIRS
+SKIP_DIRS[abi]=""
+# TODO: add "ABISupport" build tag to pkg/api
+SKIP_DIRS[tunnel]="pkg/api"
[[ $1 == run ]] && shift
for i in tunnel abi; do
- echo Build Tags: ${BUILD_TAGS[$i]}
- golangci-lint run --build-tags=${BUILD_TAGS[$i]} "$@"
+ echo ""
+ echo Running golangci-lint for "$i"
+ echo Build Tags "$i": ${BUILD_TAGS[$i]}
+ echo Skipped directories "$i": ${SKIP_DIRS[$i]}
+ golangci-lint run --build-tags=${BUILD_TAGS[$i]} --skip-dirs=${SKIP_DIRS[$i]} "$@"
done
diff --git a/install.md b/install.md
index 12dc62b32..2ef6eae2c 100644
--- a/install.md
+++ b/install.md
@@ -1,5 +1,5 @@
# libpod Installation Instructions
-The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** in the **[podman.io](https://podman.io)** site. From the hompage, the installation instructions can be found under "Get Started->Installing Podman".
+The installation instructions for Podman and libpod now reside **[here](https://podman.io/getting-started/installation)** in the **[podman.io](https://podman.io)** site. From the homepage, the installation instructions can be found under "Get Started->Installing Podman".
The podman.io site resides in a GitHub under the Containers repository at [https://github.com/containers/podman.io](https://github.com/containers/podman.io). If you see a change that needs to happen to the installation instructions, please feel free to open a pull request there, we're always happy to have new contributors!
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 50bd9bc25..3fcf687ec 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -19,7 +19,7 @@ import (
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/util"
+ "github.com/containers/libpod/pkg/selinux"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/mount"
@@ -435,12 +435,12 @@ func (c *Container) setupStorage(ctx context.Context) error {
processLabel := containerInfo.ProcessLabel
switch {
case c.ociRuntime.SupportsKVM():
- processLabel, err = util.SELinuxKVMLabel(processLabel)
+ processLabel, err = selinux.KVMLabel(processLabel)
if err != nil {
return err
}
case c.config.Systemd:
- processLabel, err = util.SELinuxInitLabel(processLabel)
+ processLabel, err = selinux.InitLabel(processLabel)
if err != nil {
return err
}
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index c40ad45b9..8ee0fb456 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -20,11 +20,11 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/buildah/pkg/secrets"
+ "github.com/containers/common/pkg/apparmor"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/annotations"
- "github.com/containers/libpod/pkg/apparmor"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/criu"
"github.com/containers/libpod/pkg/lookup"
@@ -385,6 +385,16 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
}
}
+
+ for _, i := range c.config.Spec.Linux.Namespaces {
+ if i.Type == spec.UTSNamespace {
+ hostname := c.Hostname()
+ g.SetHostname(hostname)
+ g.AddProcessEnv("HOSTNAME", hostname)
+ break
+ }
+ }
+
if c.config.UTSNsCtr != "" {
if err := c.addNamespaceContainer(&g, UTSNS, c.config.UTSNsCtr, spec.UTSNamespace); err != nil {
return nil, err
@@ -418,15 +428,6 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
g.AddAnnotation(annotations.ContainerManager, annotations.ContainerManagerLibpod)
}
- for _, i := range c.config.Spec.Linux.Namespaces {
- if i.Type == spec.UTSNamespace {
- hostname := c.Hostname()
- g.SetHostname(hostname)
- g.AddProcessEnv("HOSTNAME", hostname)
- break
- }
- }
-
// Only add container environment variable if not already present
foundContainerEnv := false
for _, env := range g.Config.Process.Env {
@@ -583,6 +584,12 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
return errors.Wrapf(err, "error retrieving dependency %s of container %s from state", ctr, c.ID())
}
+ if specNS == spec.UTSNamespace {
+ hostname := nsCtr.Hostname()
+ g.SetHostname(hostname)
+ g.AddProcessEnv("HOSTNAME", hostname)
+ }
+
// TODO need unlocked version of this for use in pods
nsPath, err := nsCtr.NamespacePath(ns)
if err != nil {
diff --git a/libpod/define/config.go b/libpod/define/config.go
index 17d764c65..692eafb04 100644
--- a/libpod/define/config.go
+++ b/libpod/define/config.go
@@ -6,10 +6,6 @@ import (
)
var (
- // DefaultInfraImage to use for infra container
- DefaultInfraImage = "k8s.gcr.io/pause:3.2"
- // DefaultInfraCommand to be run in an infra container
- DefaultInfraCommand = "/pause"
// DefaultSHMLockPath is the default path for SHM locks
DefaultSHMLockPath = "/libpod_lock"
// DefaultRootlessSHMLockPath is the default path for rootless SHM locks
diff --git a/libpod/define/errors.go b/libpod/define/errors.go
index 3ba343789..16df2a1cc 100644
--- a/libpod/define/errors.go
+++ b/libpod/define/errors.go
@@ -141,4 +141,7 @@ var (
// ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH)
// is out of date for the current podman version
ErrConmonOutdated = errors.New("outdated conmon version")
+
+ // ErrImageInUse indicates the requested operation failed because the image was in use
+ ErrImageInUse = errors.New("image is being used")
)
diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go
index 8558c149b..26fd2cab4 100644
--- a/libpod/define/pod_inspect.go
+++ b/libpod/define/pod_inspect.go
@@ -18,6 +18,8 @@ type InspectPodData struct {
Namespace string `json:"Namespace,omitempty"`
// Created is the time when the pod was created.
Created time.Time
+ // State represents the current state of the pod.
+ State string `json:"State"`
// Hostname is the hostname that the pod will set.
Hostname string
// Labels is a set of key-value labels that have been applied to the
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
index daddb6561..aec5fa4e0 100644
--- a/libpod/healthcheck.go
+++ b/libpod/healthcheck.go
@@ -238,7 +238,7 @@ func (c *Container) updateHealthCheckLog(hcl define.HealthCheckLog, inStartPerio
// 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")
+ return filepath.Join(filepath.Dir(c.state.RunDir), "healthcheck.log")
}
// GetHealthCheckLog returns HealthCheck results by reading the container's
diff --git a/libpod/image/docker_registry_options.go b/libpod/image/docker_registry_options.go
index 62a4af465..01b5558af 100644
--- a/libpod/image/docker_registry_options.go
+++ b/libpod/image/docker_registry_options.go
@@ -3,9 +3,9 @@ package image
import (
"fmt"
+ "github.com/containers/buildah/pkg/parse"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/types"
-
podmanVersion "github.com/containers/libpod/version"
)
@@ -41,6 +41,7 @@ func (o DockerRegistryOptions) GetSystemContext(parent *types.SystemContext, add
DockerArchiveAdditionalTags: additionalDockerArchiveTags,
OSChoice: o.OSChoice,
ArchitectureChoice: o.ArchitectureChoice,
+ BigFilesTemporaryDir: parse.GetTempDir(),
}
if parent != nil {
sc.SignaturePolicyPath = parent.SignaturePolicyPath
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 80cc6f15a..60787b826 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -32,10 +32,10 @@ import (
"github.com/containers/libpod/pkg/registries"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
- "github.com/opencontainers/go-digest"
+ 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"
+ opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -847,22 +847,97 @@ func (i *Image) Dangling() bool {
return len(i.Names()) == 0
}
+// Intermediate returns true if the image is cache or intermediate image.
+// Cache image has parent and child.
+func (i *Image) Intermediate(ctx context.Context) (bool, error) {
+ parent, err := i.IsParent(ctx)
+ if err != nil {
+ return false, err
+ }
+ if !parent {
+ return false, nil
+ }
+ img, err := i.GetParent(ctx)
+ if err != nil {
+ return false, err
+ }
+ if img != nil {
+ return true, nil
+ }
+ return false, nil
+}
+
+// User returns the image's user
+func (i *Image) User(ctx context.Context) (string, error) {
+ imgInspect, err := i.inspect(ctx, false)
+ if err != nil {
+ return "", err
+ }
+ return imgInspect.Config.User, nil
+}
+
+// StopSignal returns the image's StopSignal
+func (i *Image) StopSignal(ctx context.Context) (string, error) {
+ imgInspect, err := i.inspect(ctx, false)
+ if err != nil {
+ return "", err
+ }
+ return imgInspect.Config.StopSignal, nil
+}
+
+// WorkingDir returns the image's WorkingDir
+func (i *Image) WorkingDir(ctx context.Context) (string, error) {
+ imgInspect, err := i.inspect(ctx, false)
+ if err != nil {
+ return "", err
+ }
+ return imgInspect.Config.WorkingDir, nil
+}
+
+// Cmd returns the image's cmd
+func (i *Image) Cmd(ctx context.Context) ([]string, error) {
+ imgInspect, err := i.inspect(ctx, false)
+ if err != nil {
+ return nil, err
+ }
+ return imgInspect.Config.Cmd, nil
+}
+
+// Entrypoint returns the image's entrypoint
+func (i *Image) Entrypoint(ctx context.Context) ([]string, error) {
+ imgInspect, err := i.inspect(ctx, false)
+ if err != nil {
+ return nil, err
+ }
+ return imgInspect.Config.Entrypoint, nil
+}
+
+// Env returns the image's env
+func (i *Image) Env(ctx context.Context) ([]string, error) {
+ imgInspect, err := i.imageInspectInfo(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return imgInspect.Env, nil
+}
+
// Labels returns the image's labels
func (i *Image) Labels(ctx context.Context) (map[string]string, error) {
imgInspect, err := i.imageInspectInfo(ctx)
if err != nil {
- return nil, nil
+ return nil, err
}
return imgInspect.Labels, nil
}
// GetLabel Returns a case-insensitive match of a given label
func (i *Image) GetLabel(ctx context.Context, label string) (string, error) {
- imageLabels, err := i.Labels(ctx)
+ labels, err := i.Labels(ctx)
if err != nil {
return "", err
}
- for k, v := range imageLabels {
+
+ for k, v := range labels {
if strings.ToLower(k) == strings.ToLower(label) {
return v, nil
}
@@ -1412,14 +1487,14 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
}
manifestType = manifest.DockerV2Schema2MediaType
case "docker-archive", "":
- dst := output
destImageName := imageNameForSaveDestination(i, source)
- if destImageName != "" {
- dst = fmt.Sprintf("%s:%s", dst, destImageName)
+ ref, err := dockerArchiveDstReference(destImageName)
+ if err != nil {
+ return err
}
- destRef, err = dockerarchive.ParseReference(dst) // FIXME? Add dockerarchive.NewReference
+ destRef, err = dockerarchive.NewReference(output, ref)
if err != nil {
- return errors.Wrapf(err, "error getting Docker archive ImageReference for %q", dst)
+ return errors.Wrapf(err, "error getting Docker archive ImageReference for %s:%v", output, ref)
}
default:
return errors.Errorf("unknown format option %q", format)
@@ -1439,6 +1514,23 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
return nil
}
+// dockerArchiveDestReference returns a NamedTagged reference for a tagged image and nil for untagged image.
+func dockerArchiveDstReference(normalizedInput string) (reference.NamedTagged, error) {
+ if normalizedInput == "" {
+ return nil, nil
+ }
+ ref, err := reference.ParseNormalizedNamed(normalizedInput)
+ if err != nil {
+ return nil, errors.Wrapf(err, "docker-archive parsing reference %s", normalizedInput)
+ }
+ ref = reference.TagNameOnly(ref)
+ namedTagged, isTagged := ref.(reference.NamedTagged)
+ if !isTagged {
+ namedTagged = nil
+ }
+ return namedTagged, nil
+}
+
// GetConfigBlob returns a schema2image. If the image is not a schema2, then
// it will return an error
func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) {
diff --git a/libpod/image/manifests.go b/libpod/image/manifests.go
index 9dbeb4cc5..7ca17f86c 100644
--- a/libpod/image/manifests.go
+++ b/libpod/image/manifests.go
@@ -19,6 +19,7 @@ type ManifestAddOpts struct {
Arch string `json:"arch"`
Features []string `json:"features"`
Images []string `json:"images"`
+ OS string `json:"os"`
OSVersion string `json:"os_version"`
Variant string `json:"variant"`
}
@@ -86,6 +87,11 @@ func addManifestToList(ref types.ImageReference, list manifests.List, systemCont
if err != nil {
return nil, err
}
+ if opts.OS != "" {
+ if err := list.SetOS(d, opts.OS); err != nil {
+ return nil, err
+ }
+ }
if len(opts.OSVersion) > 0 {
if err := list.SetOSVersion(d, opts.OSVersion); err != nil {
return nil, err
diff --git a/libpod/image/prune.go b/libpod/image/prune.go
index 3afff22af..3b4ea74c4 100644
--- a/libpod/image/prune.go
+++ b/libpod/image/prune.go
@@ -57,7 +57,7 @@ func generatePruneFilterFuncs(filter, filterValue string) (ImageFilter, error) {
}
// GetPruneImages returns a slice of images that have no names/unused
-func (ir *Runtime) GetPruneImages(all bool, filterFuncs []ImageFilter) ([]*Image, error) {
+func (ir *Runtime) GetPruneImages(ctx context.Context, all bool, filterFuncs []ImageFilter) ([]*Image, error) {
var (
pruneImages []*Image
)
@@ -74,10 +74,6 @@ func (ir *Runtime) GetPruneImages(all bool, filterFuncs []ImageFilter) ([]*Image
}
}
- if len(i.Names()) == 0 {
- pruneImages = append(pruneImages, i)
- continue
- }
if all {
containers, err := i.Containers()
if err != nil {
@@ -85,8 +81,22 @@ func (ir *Runtime) GetPruneImages(all bool, filterFuncs []ImageFilter) ([]*Image
}
if len(containers) < 1 {
pruneImages = append(pruneImages, i)
+ continue
}
}
+
+ //skip the cache or intermediate images
+ intermediate, err := i.Intermediate(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if intermediate {
+ continue
+ }
+
+ if i.Dangling() {
+ pruneImages = append(pruneImages, i)
+ }
}
return pruneImages, nil
}
@@ -111,7 +121,7 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool, filter []string) (
filterFuncs = append(filterFuncs, generatedFunc)
}
- pruneImages, err := ir.GetPruneImages(all, filterFuncs)
+ pruneImages, err := ir.GetPruneImages(ctx, all, filterFuncs)
if err != nil {
return nil, errors.Wrap(err, "unable to get images to prune")
}
diff --git a/libpod/image/pull.go b/libpod/image/pull.go
index fd359d593..6b4c40ba2 100644
--- a/libpod/image/pull.go
+++ b/libpod/image/pull.go
@@ -334,11 +334,11 @@ func (ir *Runtime) doPullImage(ctx context.Context, sc *types.SystemContext, goa
// If the image passed in was fully-qualified, we will have 1 refpair. Bc the image is fq'd, we don't need to yap about registries.
if !goal.usedSearchRegistries {
if pullErrors != nil && len(pullErrors.Errors) > 0 { // this should always be true
- return nil, errors.Wrap(pullErrors.Errors[0], "unable to pull image")
+ return nil, pullErrors.Errors[0]
}
return nil, errors.Errorf("unable to pull image, or you do not have pull access")
}
- return nil, pullErrors
+ return nil, errors.Cause(pullErrors)
}
if len(images) > 0 {
ir.newImageEvent(events.Pull, images[0])
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index c3a90f481..83344ebbe 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -101,7 +101,19 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re
requestedMAC = ctr.config.StaticMAC
}
- podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC)
+ // If we are in a pod use the pod name for the network, otherwise the container name
+ var podName string
+ if ctr.PodID() != "" {
+ pod, err := r.GetPod(ctr.PodID())
+ if err == nil {
+ podName = pod.Name()
+ }
+ }
+ if podName == "" {
+ podName = ctr.Name()
+ }
+
+ podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC)
results, err := r.netPlugin.SetUpPod(podNetwork)
if err != nil {
diff --git a/libpod/options.go b/libpod/options.go
index b4e436b63..33b423bce 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1400,8 +1400,13 @@ func WithVolumeDriver(driver string) VolumeCreateOption {
if volume.valid {
return define.ErrVolumeFinalized
}
+ // only local driver is possible rn
+ if driver != define.VolumeDriverLocal {
+ return define.ErrNotImplemented
- return define.ErrNotImplemented
+ }
+ volume.config.Driver = define.VolumeDriverLocal
+ return nil
}
}
diff --git a/libpod/pod.go b/libpod/pod.go
index 4cdeb1033..b5a14c165 100644
--- a/libpod/pod.go
+++ b/libpod/pod.go
@@ -76,27 +76,6 @@ type podState struct {
InfraContainerID string
}
-// PodInspect represents the data we want to display for
-// podman pod inspect
-type PodInspect struct {
- Config *PodConfig
- State *PodInspectState
- Containers []PodContainerInfo
-}
-
-// PodInspectState contains inspect data on the pod's state
-type PodInspectState struct {
- CgroupPath string `json:"cgroupPath"`
- InfraContainerID string `json:"infraContainerID"`
- Status string `json:"status"`
-}
-
-// PodContainerInfo keeps information on a container in a pod
-type PodContainerInfo struct {
- ID string `json:"id"`
- State string `json:"state"`
-}
-
// InfraContainerConfig is the configuration for the pod's infra container
type InfraContainerConfig struct {
HasInfraContainer bool `json:"makeInfraContainer"`
diff --git a/libpod/pod_api.go b/libpod/pod_api.go
index ed4dc0727..45aa5cb8d 100644
--- a/libpod/pod_api.go
+++ b/libpod/pod_api.go
@@ -446,6 +446,7 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
if err != nil {
return nil, err
}
+ ctrStatuses := make(map[string]define.ContainerStatus, len(containers))
for _, c := range containers {
containerStatus := "unknown"
// Ignoring possible errors here because we don't want this to be
@@ -459,12 +460,18 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
Name: c.Name(),
State: containerStatus,
})
+ ctrStatuses[c.ID()] = c.state.State
+ }
+ podState, err := CreatePodStatusResults(ctrStatuses)
+ if err != nil {
+ return nil, err
}
inspectData := define.InspectPodData{
ID: p.ID(),
Name: p.Name(),
Namespace: p.Namespace(),
Created: p.CreatedTime(),
+ State: podState,
Hostname: "",
Labels: p.Labels(),
CreateCgroup: false,
diff --git a/libpod/runtime.go b/libpod/runtime.go
index 3b8f9e057..e71483ef9 100644
--- a/libpod/runtime.go
+++ b/libpod/runtime.go
@@ -733,6 +733,11 @@ func (r *Runtime) StorageConfig() storage.StoreOptions {
return r.storageConfig
}
+// GetStore returns the runtime stores
+func (r *Runtime) GetStore() storage.Store {
+ return r.store
+}
+
// DBConfig is a set of Libpod runtime configuration settings that are saved in
// a State when it is first created, and can subsequently be retrieved.
type DBConfig struct {
diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go
index 3dc8d3d0f..1d880531e 100644
--- a/libpod/runtime_ctr.go
+++ b/libpod/runtime_ctr.go
@@ -869,7 +869,8 @@ func (r *Runtime) PruneContainers(filterFuncs []ContainerFilter) (map[string]int
logrus.Error(err)
return false
}
- if state == define.ContainerStateStopped || state == define.ContainerStateExited {
+ if state == define.ContainerStateStopped || state == define.ContainerStateExited ||
+ state == define.ContainerStateCreated || state == define.ContainerStateConfigured {
return true
}
return false
diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go
index 6ac32878b..919080c42 100644
--- a/libpod/runtime_img.go
+++ b/libpod/runtime_img.go
@@ -71,7 +71,8 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
// to and untag it.
repoName, err := img.MatchRepoTag(img.InputName)
if hasChildren && errors.Cause(err) == image.ErrRepoTagNotFound {
- return nil, errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
+ return nil, errors.Wrapf(define.ErrImageInUse,
+ "unable to delete %q (cannot be forced) - image has dependent child images", img.ID())
}
if err != nil {
return nil, err
@@ -84,7 +85,8 @@ func (r *Runtime) RemoveImage(ctx context.Context, img *image.Image, force bool)
} else if len(img.Names()) > 1 && img.InputIsID() && !force {
// If the user requests to delete an image by ID and the image has multiple
// reponames and no force is applied, we error out.
- return nil, fmt.Errorf("unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
+ return nil, errors.Wrapf(define.ErrImageInUse,
+ "unable to delete %s (must force) - image is referred to in multiple tags", img.ID())
}
err = img.Remove(ctx, force)
if err != nil && errors.Cause(err) == storage.ErrImageUsedByContainer {
diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go
index be566e211..5b81e166a 100644
--- a/libpod/runtime_pod.go
+++ b/libpod/runtime_pod.go
@@ -176,8 +176,7 @@ func (r *Runtime) GetRunningPods() ([]*Pod, error) {
}
// PrunePods removes unused pods and their containers from local storage.
-// If force is given, then running pods are also included in the pruning.
-func (r *Runtime) PrunePods() (map[string]error, error) {
+func (r *Runtime) PrunePods(ctx context.Context) (map[string]error, error) {
response := make(map[string]error)
states := []string{define.PodStateStopped, define.PodStateExited}
filterFunc := func(p *Pod) bool {
diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go
index 12af40876..3d4bd4fb5 100644
--- a/pkg/api/handlers/compat/containers_create.go
+++ b/pkg/api/handlers/compat/containers_create.go
@@ -46,12 +46,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()"))
return
}
- defaultContainerConfig, err := runtime.GetConfig()
+ containerConfig, err := runtime.GetConfig()
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()"))
return
}
- cc, err := makeCreateConfig(defaultContainerConfig, input, newImage)
+ cc, err := makeCreateConfig(containerConfig, input, newImage)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()"))
return
@@ -60,7 +60,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.CreateContainer(r.Context(), w, runtime, &cc)
}
-func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
+func makeCreateConfig(containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) {
var (
err error
init bool
@@ -81,7 +81,7 @@ func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.Crea
workDir = input.WorkingDir
}
- stopTimeout := defaultContainerConfig.Engine.StopTimeout
+ stopTimeout := containerConfig.Engine.StopTimeout
if input.StopTimeout != nil {
stopTimeout = uint(*input.StopTimeout)
}
diff --git a/pkg/api/handlers/compat/containers_prune.go b/pkg/api/handlers/compat/containers_prune.go
index b4e98ac1f..9d77f612b 100644
--- a/pkg/api/handlers/compat/containers_prune.go
+++ b/pkg/api/handlers/compat/containers_prune.go
@@ -38,21 +38,24 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
filterFuncs = append(filterFuncs, generatedFunc)
}
}
- prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
- if err != nil {
- utils.InternalServerError(w, err)
- return
- }
// Libpod response differs
if utils.IsLibpodRequest(r) {
- report := &entities.ContainerPruneReport{
- Err: pruneErrors,
- ID: prunedContainers,
+ report, err := PruneContainersHelper(w, r, filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
}
+
utils.WriteResponse(w, http.StatusOK, report)
return
}
+
+ prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
for ctrID, size := range prunedContainers {
if pruneErrors[ctrID] == nil {
space += size
@@ -65,3 +68,19 @@ func PruneContainers(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, report)
}
+
+func PruneContainersHelper(w http.ResponseWriter, r *http.Request, filterFuncs []libpod.ContainerFilter) (
+ *entities.ContainerPruneReport, error) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ prunedContainers, pruneErrors, err := runtime.PruneContainers(filterFuncs)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return nil, err
+ }
+
+ report := &entities.ContainerPruneReport{
+ Err: pruneErrors,
+ ID: prunedContainers,
+ }
+ return report, nil
+}
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 8ef32716d..7ebfb0d1e 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -6,8 +6,8 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/events"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/gorilla/schema"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
@@ -70,7 +70,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
coder.SetEscapeHTML(true)
for event := range eventChannel {
- e := handlers.EventToApiEvent(event)
+ e := entities.ConvertToEntitiesEvent(*event)
if err := coder.Encode(e); err != nil {
logrus.Errorf("unable to write json: %q", err)
}
diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go
index 179b4a3e0..e9756a03f 100644
--- a/pkg/api/handlers/compat/info.go
+++ b/pkg/api/handlers/compat/info.go
@@ -10,12 +10,12 @@ import (
"time"
"github.com/containers/common/pkg/config"
+ "github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/sysinfo"
docker "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/google/uuid"
diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go
index f64132d55..40b6cacdb 100644
--- a/pkg/api/handlers/libpod/containers_create.go
+++ b/pkg/api/handlers/libpod/containers_create.go
@@ -1,6 +1,7 @@
package libpod
import (
+ "context"
"encoding/json"
"net/http"
@@ -26,7 +27,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) {
utils.InternalServerError(w, err)
return
}
- ctr, err := generate.MakeContainer(runtime, &sg)
+ ctr, err := generate.MakeContainer(context.Background(), runtime, &sg)
if err != nil {
utils.InternalServerError(w, err)
return
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index a42d06205..f7be5ce9a 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -22,6 +22,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/util"
utils2 "github.com/containers/libpod/utils"
"github.com/gorilla/schema"
@@ -111,7 +112,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) {
return
}
// libpod has additional fields that we need to populate.
- is.Created = img.Created().Unix()
+ is.Created = img.Created()
is.ReadOnly = img.IsReadOnly()
summaries[j] = is
}
@@ -282,7 +283,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) {
return
}
}
- utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Name: loadedImage})
+ utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Names: split})
}
func ImagesImport(w http.ResponseWriter, r *http.Request) {
@@ -442,7 +443,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) {
nil,
util.PullImageAlways)
if err != nil {
- utils.InternalServerError(w, errors.Wrapf(err, "error pulling image %q", query.Reference))
+ utils.InternalServerError(w, err)
return
}
res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()})
@@ -698,3 +699,30 @@ func SearchImages(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, reports)
}
+
+// ImagesRemove is the endpoint for image removal.
+func ImagesRemove(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ All bool `schema:"all"`
+ Force bool `schema:"force"`
+ Images []string `schema:"images"`
+ }{
+ All: false,
+ Force: false,
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force}
+
+ imageEngine := abi.ImageEngine{Libpod: runtime}
+ rmReport, rmError := imageEngine.Remove(r.Context(), query.Images, opts)
+ report := handlers.LibpodImagesRemoveReport{ImageRemoveReport: *rmReport, Error: rmError.Error()}
+ utils.WriteResponse(w, http.StatusOK, report)
+}
diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go
index 92556bb61..c3f8d5d66 100644
--- a/pkg/api/handlers/libpod/pods.go
+++ b/pkg/api/handlers/libpod/pods.go
@@ -11,6 +11,7 @@ import (
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/containers/libpod/pkg/util"
@@ -230,15 +231,30 @@ func PodRestart(w http.ResponseWriter, r *http.Request) {
}
func PodPrune(w http.ResponseWriter, r *http.Request) {
+ reports, err := PodPruneHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func PodPruneHelper(w http.ResponseWriter, r *http.Request) ([]*entities.PodPruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ reports []*entities.PodPruneReport
)
- pruned, err := runtime.PrunePods()
+ responses, err := runtime.PrunePods(r.Context())
if err != nil {
- utils.InternalServerError(w, err)
- return
+ return nil, err
}
- utils.WriteResponse(w, http.StatusOK, pruned)
+ for k, v := range responses {
+ reports = append(reports, &entities.PodPruneReport{
+ Err: v,
+ Id: k,
+ })
+ }
+ return reports, nil
}
func PodPause(w http.ResponseWriter, r *http.Request) {
@@ -412,3 +428,44 @@ func PodExists(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusNoContent, "")
}
+
+func PodStats(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+
+ query := struct {
+ NamesOrIDs []string `schema:"namesOrIDs"`
+ All bool `schema:"all"`
+ }{
+ // default would go here
+ }
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ // Validate input.
+ options := entities.PodStatsOptions{All: query.All}
+ if err := entities.ValidatePodStatsOptions(query.NamesOrIDs, &options); err != nil {
+ utils.InternalServerError(w, err)
+ }
+
+ // Collect the stats and send them over the wire.
+ containerEngine := abi.ContainerEngine{Libpod: runtime}
+ reports, err := containerEngine.PodStats(r.Context(), query.NamesOrIDs, options)
+
+ // Error checks as documented in swagger.
+ switch errors.Cause(err) {
+ case define.ErrNoSuchPod:
+ utils.Error(w, "one or more pods not found", http.StatusNotFound, err)
+ return
+ case nil:
+ // Nothing to do.
+ default:
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go
index ed19462c6..46426eb6b 100644
--- a/pkg/api/handlers/libpod/swagger.go
+++ b/pkg/api/handlers/libpod/swagger.go
@@ -70,6 +70,13 @@ type swagStartPodResponse struct {
Body entities.PodStartReport
}
+// Prune pod
+// swagger:response PodPruneReport
+type swagPrunePodResponse struct {
+ // in:body
+ Body entities.PodPruneReport
+}
+
// Rm pod
// swagger:response PodRmReport
type swagRmPodResponse struct {
diff --git a/pkg/api/handlers/libpod/system.go b/pkg/api/handlers/libpod/system.go
new file mode 100644
index 000000000..98e33bf10
--- /dev/null
+++ b/pkg/api/handlers/libpod/system.go
@@ -0,0 +1,71 @@
+package libpod
+
+import (
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+)
+
+// SystemPrune removes unused data
+func SystemPrune(w http.ResponseWriter, r *http.Request) {
+ var (
+ decoder = r.Context().Value("decoder").(*schema.Decoder)
+ runtime = r.Context().Value("runtime").(*libpod.Runtime)
+ systemPruneReport = new(entities.SystemPruneReport)
+ )
+ query := struct {
+ All bool `schema:"all"`
+ Volumes bool `schema:"volumes"`
+ }{}
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ podPruneReport, err := PodPruneHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.PodPruneReport = podPruneReport
+
+ // We could parallelize this, should we?
+ containerPruneReport, err := compat.PruneContainersHelper(w, r, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.ContainerPruneReport = containerPruneReport
+
+ results, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, nil)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+
+ report := entities.ImagePruneReport{
+ Report: entities.Report{
+ Id: results,
+ Err: nil,
+ },
+ }
+
+ systemPruneReport.ImagePruneReport = &report
+
+ if query.Volumes {
+ volumePruneReport, err := pruneVolumesHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ systemPruneReport.VolumePruneReport = volumePruneReport
+ }
+ utils.WriteResponse(w, http.StatusOK, systemPruneReport)
+}
diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go
index 18c561a0d..c42ca407b 100644
--- a/pkg/api/handlers/libpod/volumes.go
+++ b/pkg/api/handlers/libpod/volumes.go
@@ -147,14 +147,22 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) {
}
func PruneVolumes(w http.ResponseWriter, r *http.Request) {
+ reports, err := pruneVolumesHelper(w, r)
+ if err != nil {
+ utils.InternalServerError(w, err)
+ return
+ }
+ utils.WriteResponse(w, http.StatusOK, reports)
+}
+
+func pruneVolumesHelper(w http.ResponseWriter, r *http.Request) ([]*entities.VolumePruneReport, error) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
reports []*entities.VolumePruneReport
)
pruned, err := runtime.PruneVolumes(r.Context())
if err != nil {
- utils.InternalServerError(w, err)
- return
+ return nil, err
}
for k, v := range pruned {
reports = append(reports, &entities.VolumePruneReport{
@@ -162,9 +170,8 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) {
Id: k,
})
}
- utils.WriteResponse(w, http.StatusOK, reports)
+ return reports, nil
}
-
func RemoveVolume(w http.ResponseWriter, r *http.Request) {
var (
runtime = r.Context().Value("runtime").(*libpod.Runtime)
diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go
index ba97a4755..0aceaf5f6 100644
--- a/pkg/api/handlers/swagger/swagger.go
+++ b/pkg/api/handlers/swagger/swagger.go
@@ -49,6 +49,13 @@ type swagLibpodImagesPullResponse struct {
Body handlers.LibpodImagesPullReport
}
+// Remove response
+// swagger:response DocsLibpodImagesRemoveResponse
+type swagLibpodImagesRemoveResponse struct {
+ // in:body
+ Body handlers.LibpodImagesRemoveReport
+}
+
// Delete response
// swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct {
@@ -115,6 +122,13 @@ type swagPodTopResponse struct {
}
}
+// List processes in pod
+// swagger:response DocsPodStatsResponse
+type swagPodStatsResponse struct {
+ // in:body
+ Body []*entities.PodStatsReport
+}
+
// Inspect container
// swagger:response LibpodInspectContainerResponse
type swagLibpodInspectContainerResponse struct {
@@ -136,7 +150,7 @@ type swagListPodsResponse struct {
type swagInspectPodResponse struct {
// in:body
Body struct {
- libpod.PodInspect
+ define.InspectPodData
}
}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index f402da064..58a12ea6a 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -4,16 +4,13 @@ import (
"context"
"encoding/json"
"fmt"
- "strconv"
"time"
"github.com/containers/image/v5/manifest"
- "github.com/containers/libpod/libpod/events"
libpodImage "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/domain/entities"
docker "github.com/docker/docker/api/types"
dockerContainer "github.com/docker/docker/api/types/container"
- dockerEvents "github.com/docker/docker/api/types/events"
dockerNetwork "github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
@@ -39,6 +36,14 @@ type LibpodImagesPullReport struct {
ID string `json:"id"`
}
+// LibpodImagesRemoveReport is the return type for image removal via the rest
+// api.
+type LibpodImagesRemoveReport struct {
+ entities.ImageRemoveReport
+ // Image removal requires is to return data and an error.
+ Error string
+}
+
type ContainersPruneReport struct {
docker.ContainersPruneReport
}
@@ -143,10 +148,6 @@ type PodCreateConfig struct {
Share string `json:"share"`
}
-type Event struct {
- dockerEvents.Message
-}
-
type HistoryResponse struct {
ID string `json:"Id"`
Created int64 `json:"Created"`
@@ -173,49 +174,6 @@ type ExecCreateResponse struct {
docker.IDResponse
}
-func (e *Event) ToLibpodEvent() *events.Event {
- exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
- if err != nil {
- return nil
- }
- status, err := events.StringToStatus(e.Action)
- if err != nil {
- return nil
- }
- t, err := events.StringToType(e.Type)
- if err != nil {
- return nil
- }
- lp := events.Event{
- ContainerExitCode: exitCode,
- ID: e.Actor.ID,
- Image: e.Actor.Attributes["image"],
- Name: e.Actor.Attributes["name"],
- Status: status,
- Time: time.Unix(e.Time, e.TimeNano),
- Type: t,
- }
- return &lp
-}
-
-func EventToApiEvent(e *events.Event) *Event {
- return &Event{dockerEvents.Message{
- Type: e.Type.String(),
- Action: e.Status.String(),
- Actor: dockerEvents.Actor{
- ID: e.ID,
- Attributes: map[string]string{
- "image": e.Image,
- "name": e.Name,
- "containerExitCode": strconv.Itoa(e.ContainerExitCode),
- },
- },
- Scope: "local",
- Time: e.Time.Unix(),
- TimeNano: e.Time.UnixNano(),
- }}
-}
-
func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
containers, err := l.Containers()
if err != nil {
@@ -262,7 +220,7 @@ func ImageToImageSummary(l *libpodImage.Image) (*entities.ImageSummary, error) {
ID: l.ID(),
ParentId: l.Parent,
RepoTags: repoTags,
- Created: l.Created().Unix(),
+ Created: l.Created(),
Size: int64(*size),
SharedSize: 0,
VirtualSize: l.VirtualSize,
@@ -311,7 +269,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI
// NetworkDisabled: false,
// MacAddress: "",
// OnBuild: nil,
- // Labels: nil,
+ Labels: info.Labels,
// StopSignal: "",
// StopTimeout: nil,
// Shell: nil,
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index aafc64353..3253a9be3 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -14,6 +14,9 @@ var (
ErrLinkNotSupport = errors.New("Link is not supported")
)
+// TODO: document the exported functions in this file and make them more
+// generic (e.g., not tied to one ctr/pod).
+
// Error formats an API response to an error
//
// apiMessage and code must match the container API, and are sent to client
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 6cc6f0cfa..f59dca6f5 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -822,6 +822,38 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// 500:
// $ref: '#/responses/InternalError'
r.Handle(VersionedPath("/libpod/images/import"), s.APIHandler(libpod.ImagesImport)).Methods(http.MethodPost)
+ // swagger:operation GET /libpod/images/remove libpod libpodImagesRemove
+ // ---
+ // tags:
+ // - images
+ // summary: Remove one or more images from the storage.
+ // description: Remove one or more images from the storage.
+ // parameters:
+ // - in: query
+ // name: images
+ // description: Images IDs or names to remove.
+ // type: array
+ // items:
+ // type: string
+ // - in: query
+ // name: all
+ // description: Remove all images.
+ // type: boolean
+ // default: true
+ // - in: query
+ // name: force
+ // description: Force image removal (including containers using the images).
+ // type: boolean
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsLibpodImagesRemoveResponse"
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: '#/responses/InternalError'
+ r.Handle(VersionedPath("/libpod/images/remove"), s.APIHandler(libpod.ImagesRemove)).Methods(http.MethodGet)
// swagger:operation POST /libpod/images/pull libpod libpodImagesPull
// ---
// tags:
diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go
index a49bf1f63..4156dd86b 100644
--- a/pkg/api/server/register_pods.go
+++ b/pkg/api/server/register_pods.go
@@ -53,11 +53,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// - application/json
// responses:
// 200:
- // description: tbd
- // schema:
- // type: object
- // additionalProperties:
- // type: string
+ // $ref: '#/responses/PodPruneReport'
// 400:
// $ref: "#/responses/BadParamError"
// 409:
@@ -290,9 +286,36 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error {
// 200:
// $ref: "#/responses/DocsPodTopResponse"
// 404:
- // $ref: "#/responses/NoSuchContainer"
+ // $ref: "#/responses/NoSuchPod"
// 500:
// $ref: "#/responses/InternalError"
r.Handle(VersionedPath("/libpod/pods/{name}/top"), s.APIHandler(libpod.PodTop)).Methods(http.MethodGet)
+ // swagger:operation GET /libpod/pods/stats pods statsPod
+ // ---
+ // tags:
+ // - pods
+ // summary: Get stats for one or more pods
+ // description: Display a live stream of resource usage statistics for the containers in one or more pods
+ // parameters:
+ // - in: query
+ // name: all
+ // description: Provide statistics for all running pods.
+ // type: boolean
+ // - in: query
+ // name: namesOrIDs
+ // description: Names or IDs of pods.
+ // type: array
+ // items:
+ // type: string
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: "#/responses/DocsPodTopResponse"
+ // 404:
+ // $ref: "#/responses/NoSuchPod"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/pods/stats"), s.APIHandler(libpod.PodStats)).Methods(http.MethodGet)
return nil
}
diff --git a/pkg/api/server/register_system.go b/pkg/api/server/register_system.go
index 708ccd39b..7375a75c1 100644
--- a/pkg/api/server/register_system.go
+++ b/pkg/api/server/register_system.go
@@ -4,6 +4,7 @@ import (
"net/http"
"github.com/containers/libpod/pkg/api/handlers/compat"
+ "github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
@@ -11,5 +12,21 @@ func (s *APIServer) registerSystemHandlers(r *mux.Router) error {
r.Handle(VersionedPath("/system/df"), s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
// Added non version path to URI to support docker non versioned paths
r.Handle("/system/df", s.APIHandler(compat.GetDiskUsage)).Methods(http.MethodGet)
+ // Swagger:operation POST /libpod/system/prune libpod pruneSystem
+ // ---
+ // tags:
+ // - system
+ // summary: Prune unused data
+ // produces:
+ // - application/json
+ // responses:
+ // 200:
+ // $ref: '#/responses/SystemPruneReport'
+ // 400:
+ // $ref: "#/responses/BadParamError"
+ // 500:
+ // $ref: "#/responses/InternalError"
+ r.Handle(VersionedPath("/libpod/system/prune"), s.APIHandler(libpod.SystemPrune)).Methods(http.MethodPost)
+
return nil
}
diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go
index 5f1a86183..ce2d152e0 100644
--- a/pkg/api/server/server.go
+++ b/pkg/api/server/server.go
@@ -8,6 +8,7 @@ import (
"os"
"os/signal"
"runtime"
+ goRuntime "runtime"
"strings"
"sync"
"syscall"
@@ -30,6 +31,7 @@ type APIServer struct {
net.Listener // mux for routing HTTP API calls to libpod routines
context.CancelFunc // Stop APIServer
idleTracker *IdleTracker // Track connections to support idle shutdown
+ pprof *http.Server // Sidecar http server for providing performance data
}
// Number of seconds to wait for next request, if exceeded shutdown server
@@ -51,7 +53,7 @@ func NewServerWithSettings(runtime *libpod.Runtime, duration time.Duration, list
func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Listener) (*APIServer, error) {
// If listener not provided try socket activation protocol
if listener == nil {
- if _, found := os.LookupEnv("LISTEN_FDS"); !found {
+ if _, found := os.LookupEnv("LISTEN_PID"); !found {
return nil, errors.Errorf("Cannot create API Server, no listener provided and socket activation protocol is not active.")
}
@@ -125,7 +127,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
if err != nil {
methods = []string{"<N/A>"}
}
- logrus.Debugf("Methods: %s Path: %s", strings.Join(methods, ", "), path)
+ logrus.Debugf("Methods: %6s Path: %s", strings.Join(methods, ", "), path)
return nil
})
}
@@ -145,6 +147,20 @@ func (s *APIServer) Serve() error {
_ = s.Shutdown()
}()
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ go func() {
+ pprofMux := mux.NewRouter()
+ pprofMux.PathPrefix("/debug/pprof").Handler(http.DefaultServeMux)
+ goRuntime.SetMutexProfileFraction(1)
+ goRuntime.SetBlockProfileRate(1)
+ s.pprof = &http.Server{Addr: "localhost:8888", Handler: pprofMux}
+ err := s.pprof.ListenAndServe()
+ if err != nil && err != http.ErrServerClosed {
+ logrus.Warn("Profiler Service failed: " + err.Error())
+ }
+ }()
+ }
+
go func() {
err := s.Server.Serve(s.Listener)
if err != nil && err != http.ErrServerClosed {
@@ -166,25 +182,29 @@ func (s *APIServer) Serve() error {
// Shutdown is a clean shutdown waiting on existing clients
func (s *APIServer) Shutdown() error {
+ if s.idleTracker.Duration == UnlimitedServiceDuration {
+ logrus.Debug("APIServer.Shutdown ignored as Duration is UnlimitedService.")
+ return nil
+ }
+
+ // Gracefully shutdown server(s), duration of wait same as idle window
+ // TODO: Should we really wait the idle window for shutdown?
+ ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration)
+ defer cancel()
+
if logrus.IsLevelEnabled(logrus.DebugLevel) {
_, file, line, _ := runtime.Caller(1)
logrus.Debugf("APIServer.Shutdown by %s:%d, %d/%d connection(s)",
file, line, s.idleTracker.ActiveConnections(), s.idleTracker.TotalConnections())
+ if err := s.pprof.Shutdown(ctx); err != nil {
+ logrus.Warn("Failed to cleanly shutdown pprof Server: " + err.Error())
+ }
}
- // Duration == 0 flags no auto-shutdown of the server
- if s.idleTracker.Duration == 0 {
- logrus.Debug("APIServer.Shutdown ignored as Duration == 0")
- return nil
- }
-
- // Gracefully shutdown server, duration of wait same as idle window
- ctx, cancel := context.WithTimeout(context.Background(), s.idleTracker.Duration)
- defer cancel()
go func() {
err := s.Server.Shutdown(ctx)
if err != nil && err != context.Canceled && err != http.ErrServerClosed {
- logrus.Errorf("Failed to cleanly shutdown APIServer: %s", err.Error())
+ logrus.Error("Failed to cleanly shutdown APIServer: " + err.Error())
}
}()
<-ctx.Done()
diff --git a/pkg/api/types/types.go b/pkg/api/types/types.go
new file mode 100644
index 000000000..1b91364e3
--- /dev/null
+++ b/pkg/api/types/types.go
@@ -0,0 +1,9 @@
+package types
+
+const (
+ // DefaultAPIVersion is the version of the API the server defaults to.
+ DefaultAPIVersion = "1.40" // See https://docs.docker.com/engine/api/v1.40/
+
+ // DefaultAPIVersion is the minimal required version of the API.
+ MinimalAPIVersion = "1.24"
+)
diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go
deleted file mode 100644
index 8e17361cb..000000000
--- a/pkg/apparmor/apparmor.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package apparmor
-
-import (
- "errors"
-
- "github.com/containers/common/pkg/config"
- libpodVersion "github.com/containers/libpod/version"
-)
-
-var (
- // DefaultLipodProfilePrefix is used for version-independent presence checks.
- DefaultLipodProfilePrefix = config.DefaultApparmorProfile
- // DefaultLibpodProfile is the name of default libpod AppArmor profile.
- DefaultLibpodProfile = DefaultLipodProfilePrefix + "-" + libpodVersion.Version
- // ErrApparmorUnsupported indicates that AppArmor support is not supported.
- ErrApparmorUnsupported = errors.New("AppArmor is not supported")
- // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode.
- ErrApparmorRootless = errors.New("AppArmor is not supported in rootless mode")
-)
diff --git a/pkg/apparmor/apparmor_linux.go b/pkg/apparmor/apparmor_linux.go
deleted file mode 100644
index 33710ff56..000000000
--- a/pkg/apparmor/apparmor_linux.go
+++ /dev/null
@@ -1,289 +0,0 @@
-// +build linux,apparmor
-
-package apparmor
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "os"
- "os/exec"
- "path"
- "strconv"
- "strings"
- "text/template"
-
- "github.com/containers/libpod/pkg/rootless"
- runcaa "github.com/opencontainers/runc/libcontainer/apparmor"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
-)
-
-// profileDirectory is the file store for apparmor profiles and macros.
-var profileDirectory = "/etc/apparmor.d"
-
-// IsEnabled returns true if AppArmor is enabled on the host.
-func IsEnabled() bool {
- if rootless.IsRootless() {
- return false
- }
- return runcaa.IsEnabled()
-}
-
-// profileData holds information about the given profile for generation.
-type profileData struct {
- // Name is profile name.
- Name string
- // Imports defines the apparmor functions to import, before defining the profile.
- Imports []string
- // InnerImports defines the apparmor functions to import in the profile.
- InnerImports []string
- // Version is the {major, minor, patch} version of apparmor_parser as a single number.
- Version int
-}
-
-// generateDefault creates an apparmor profile from ProfileData.
-func (p *profileData) generateDefault(out io.Writer) error {
- compiled, err := template.New("apparmor_profile").Parse(libpodProfileTemplate)
- if err != nil {
- return err
- }
-
- if macroExists("tunables/global") {
- p.Imports = append(p.Imports, "#include <tunables/global>")
- } else {
- p.Imports = append(p.Imports, "@{PROC}=/proc/")
- }
-
- if macroExists("abstractions/base") {
- p.InnerImports = append(p.InnerImports, "#include <abstractions/base>")
- }
-
- ver, err := getAAParserVersion()
- if err != nil {
- return err
- }
- p.Version = ver
-
- return compiled.Execute(out, p)
-}
-
-// macrosExists checks if the passed macro exists.
-func macroExists(m string) bool {
- _, err := os.Stat(path.Join(profileDirectory, m))
- return err == nil
-}
-
-// InstallDefault generates a default profile and loads it into the kernel
-// using 'apparmor_parser'.
-func InstallDefault(name string) error {
- if rootless.IsRootless() {
- return ErrApparmorRootless
- }
-
- p := profileData{
- Name: name,
- }
-
- cmd := exec.Command("apparmor_parser", "-Kr")
- pipe, err := cmd.StdinPipe()
- if err != nil {
- return err
- }
- if err := cmd.Start(); err != nil {
- if pipeErr := pipe.Close(); pipeErr != nil {
- logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
- }
- return err
- }
- if err := p.generateDefault(pipe); err != nil {
- if pipeErr := pipe.Close(); pipeErr != nil {
- logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
- }
- if cmdErr := cmd.Wait(); cmdErr != nil {
- logrus.Errorf("unable to wait for apparmor command: %q", cmdErr)
- }
- return err
- }
-
- if pipeErr := pipe.Close(); pipeErr != nil {
- logrus.Errorf("unable to close apparmor pipe: %q", pipeErr)
- }
- return cmd.Wait()
-}
-
-// DefaultContent returns the default profile content as byte slice. The
-// profile is named as the provided `name`. The function errors if the profile
-// generation fails.
-func DefaultContent(name string) ([]byte, error) {
- p := profileData{Name: name}
- var bytes bytes.Buffer
- if err := p.generateDefault(&bytes); err != nil {
- return nil, err
- }
- return bytes.Bytes(), nil
-}
-
-// IsLoaded checks if a profile with the given name has been loaded into the
-// kernel.
-func IsLoaded(name string) (bool, error) {
- if name != "" && rootless.IsRootless() {
- return false, errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
- }
-
- file, err := os.Open("/sys/kernel/security/apparmor/profiles")
- if err != nil {
- if os.IsNotExist(err) {
- return false, nil
- }
- return false, err
- }
- defer file.Close()
-
- r := bufio.NewReader(file)
- for {
- p, err := r.ReadString('\n')
- if err == io.EOF {
- break
- }
- if err != nil {
- return false, err
- }
- if strings.HasPrefix(p, name+" ") {
- return true, nil
- }
- }
-
- return false, nil
-}
-
-// execAAParser runs `apparmor_parser` with the passed arguments.
-func execAAParser(dir string, args ...string) (string, error) {
- c := exec.Command("apparmor_parser", args...)
- c.Dir = dir
-
- output, err := c.CombinedOutput()
- if err != nil {
- return "", fmt.Errorf("running `%s %s` failed with output: %s\nerror: %v", c.Path, strings.Join(c.Args, " "), output, err)
- }
-
- return string(output), nil
-}
-
-// getAAParserVersion returns the major and minor version of apparmor_parser.
-func getAAParserVersion() (int, error) {
- output, err := execAAParser("", "--version")
- if err != nil {
- return -1, err
- }
- return parseAAParserVersion(output)
-}
-
-// parseAAParserVersion parses the given `apparmor_parser --version` output and
-// returns the major and minor version number as an integer.
-func parseAAParserVersion(output string) (int, error) {
- // output is in the form of the following:
- // AppArmor parser version 2.9.1
- // Copyright (C) 1999-2008 Novell Inc.
- // Copyright 2009-2012 Canonical Ltd.
- lines := strings.SplitN(output, "\n", 2)
- words := strings.Split(lines[0], " ")
- version := words[len(words)-1]
-
- // split by major minor version
- v := strings.Split(version, ".")
- if len(v) == 0 || len(v) > 3 {
- return -1, fmt.Errorf("parsing version failed for output: `%s`", output)
- }
-
- // Default the versions to 0.
- var majorVersion, minorVersion, patchLevel int
-
- majorVersion, err := strconv.Atoi(v[0])
- if err != nil {
- return -1, err
- }
-
- if len(v) > 1 {
- minorVersion, err = strconv.Atoi(v[1])
- if err != nil {
- return -1, err
- }
- }
- if len(v) > 2 {
- patchLevel, err = strconv.Atoi(v[2])
- if err != nil {
- return -1, err
- }
- }
-
- // major*10^5 + minor*10^3 + patch*10^0
- numericVersion := majorVersion*1e5 + minorVersion*1e3 + patchLevel
- return numericVersion, nil
-
-}
-
-// CheckProfileAndLoadDefault checks if the specified profile is loaded and
-// loads the DefaultLibpodProfile if the specified on is prefixed by
-// DefaultLipodProfilePrefix. This allows to always load and apply the latest
-// default AppArmor profile. Note that AppArmor requires root. If it's a
-// default profile, return DefaultLipodProfilePrefix, otherwise the specified
-// one.
-func CheckProfileAndLoadDefault(name string) (string, error) {
- if name == "unconfined" {
- return name, nil
- }
-
- // AppArmor is not supported in rootless mode as it requires root
- // privileges. Return an error in case a specific profile is specified.
- if rootless.IsRootless() {
- if name != "" {
- return "", errors.Wrapf(ErrApparmorRootless, "cannot load AppArmor profile %q", name)
- } else {
- logrus.Debug("skipping loading default AppArmor profile (rootless mode)")
- return "", nil
- }
- }
-
- // Check if AppArmor is disabled and error out if a profile is to be set.
- if !runcaa.IsEnabled() {
- if name == "" {
- return "", nil
- } else {
- return "", fmt.Errorf("profile %q specified but AppArmor is disabled on the host", name)
- }
- }
-
- // If the specified name is not empty or is not a default libpod one,
- // ignore it and return the name.
- if name != "" && !strings.HasPrefix(name, DefaultLipodProfilePrefix) {
- isLoaded, err := IsLoaded(name)
- if err != nil {
- return "", err
- }
- if !isLoaded {
- return "", fmt.Errorf("AppArmor profile %q specified but not loaded", name)
- }
- return name, nil
- }
-
- name = DefaultLibpodProfile
- // To avoid expensive redundant loads on each invocation, check
- // if it's loaded before installing it.
- isLoaded, err := IsLoaded(name)
- if err != nil {
- return "", err
- }
- if !isLoaded {
- err = InstallDefault(name)
- if err != nil {
- return "", err
- }
- logrus.Infof("successfully loaded AppAmor profile %q", name)
- } else {
- logrus.Infof("AppAmor profile %q is already loaded", name)
- }
-
- return name, nil
-}
diff --git a/pkg/apparmor/apparmor_linux_template.go b/pkg/apparmor/apparmor_linux_template.go
deleted file mode 100644
index 8d9a92ef7..000000000
--- a/pkg/apparmor/apparmor_linux_template.go
+++ /dev/null
@@ -1,49 +0,0 @@
-// +build linux,apparmor
-
-package apparmor
-
-const libpodProfileTemplate = `
-{{range $value := .Imports}}
-{{$value}}
-{{end}}
-
-profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
-{{range $value := .InnerImports}}
- {{$value}}
-{{end}}
-
- network,
- capability,
- file,
- umount,
-
-{{if ge .Version 208096}}
- # Allow signals from privileged profiles and from within the same profile
- signal (receive) peer=unconfined,
- signal (send,receive) peer={{.Name}},
-{{end}}
-
- deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
- # deny write to files not in /proc/<number>/** or /proc/sys/**
- deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
- deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
- deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
- deny @{PROC}/sysrq-trigger rwklx,
- deny @{PROC}/kcore rwklx,
-
- deny mount,
-
- deny /sys/[^f]*/** wklx,
- deny /sys/f[^s]*/** wklx,
- deny /sys/fs/[^c]*/** wklx,
- deny /sys/fs/c[^g]*/** wklx,
- deny /sys/fs/cg[^r]*/** wklx,
- deny /sys/firmware/** rwklx,
- deny /sys/kernel/security/** rwklx,
-
-{{if ge .Version 208095}}
- # suppress ptrace denials when using using 'ps' inside a container
- ptrace (trace,read) peer={{.Name}},
-{{end}}
-}
-`
diff --git a/pkg/apparmor/apparmor_linux_test.go b/pkg/apparmor/apparmor_linux_test.go
deleted file mode 100644
index 3ff6e18bc..000000000
--- a/pkg/apparmor/apparmor_linux_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-// +build linux,apparmor
-
-package apparmor
-
-import (
- "os"
- "testing"
-)
-
-type versionExpected struct {
- output string
- version int
-}
-
-func TestParseAAParserVersion(t *testing.T) {
- if !IsEnabled() {
- t.Skip("AppArmor disabled: skipping tests")
- }
- versions := []versionExpected{
- {
- output: `AppArmor parser version 2.10
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 210000,
- },
- {
- output: `AppArmor parser version 2.8
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 208000,
- },
- {
- output: `AppArmor parser version 2.20
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 220000,
- },
- {
- output: `AppArmor parser version 2.05
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 205000,
- },
- {
- output: `AppArmor parser version 2.9.95
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 209095,
- },
- {
- output: `AppArmor parser version 3.14.159
-Copyright (C) 1999-2008 Novell Inc.
-Copyright 2009-2012 Canonical Ltd.
-
-`,
- version: 314159,
- },
- }
-
- for _, v := range versions {
- version, err := parseAAParserVersion(v.output)
- if err != nil {
- t.Fatalf("expected error to be nil for %#v, got: %v", v, err)
- }
- if version != v.version {
- t.Fatalf("expected version to be %d, was %d, for: %#v\n", v.version, version, v)
- }
- }
-}
-
-const (
- aapath = "/sys/kernel/security/apparmor/"
- profile = "libpod-default-testing"
-)
-
-func TestInstallDefault(t *testing.T) {
- if _, err := os.Stat(aapath); err != nil {
- t.Skip("AppArmor isn't available in this environment")
- }
-
- // removes `profile`
- removeProfile := func() error {
- path := aapath + ".remove"
-
- f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
- if err != nil {
- return err
- }
- defer f.Close()
-
- _, err = f.WriteString(profile)
- return err
- }
-
- // makes sure `profile` is loaded according to `state`
- checkLoaded := func(state bool) {
- loaded, err := IsLoaded(profile)
- if err != nil {
- t.Fatalf("Error searching AppArmor profile '%s': %v", profile, err)
- }
- if state != loaded {
- if state {
- t.Fatalf("AppArmor profile '%s' isn't loaded but should", profile)
- } else {
- t.Fatalf("AppArmor profile '%s' is loaded but shouldn't", profile)
- }
- }
- }
-
- // test installing the profile
- if err := InstallDefault(profile); err != nil {
- t.Fatalf("Couldn't install AppArmor profile '%s': %v", profile, err)
- }
- checkLoaded(true)
-
- // remove the profile and check again
- if err := removeProfile(); err != nil {
- t.Fatalf("Couldn't remove AppArmor profile '%s': %v", profile, err)
- }
- checkLoaded(false)
-}
-
-func TestDefaultContent(t *testing.T) {
- if _, err := os.Stat(aapath); err != nil {
- t.Skip("AppArmor isn't available in this environment")
- }
- if _, err := DefaultContent(profile); err != nil {
- t.Fatalf("Couldn't retrieve default AppArmor profile content '%s': %v", profile, err)
- }
-}
diff --git a/pkg/apparmor/apparmor_unsupported.go b/pkg/apparmor/apparmor_unsupported.go
deleted file mode 100644
index 13469f1b6..000000000
--- a/pkg/apparmor/apparmor_unsupported.go
+++ /dev/null
@@ -1,31 +0,0 @@
-// +build !linux !apparmor
-
-package apparmor
-
-// IsEnabled dummy.
-func IsEnabled() bool {
- return false
-}
-
-// InstallDefault dummy.
-func InstallDefault(name string) error {
- return ErrApparmorUnsupported
-}
-
-// IsLoaded dummy.
-func IsLoaded(name string) (bool, error) {
- return false, ErrApparmorUnsupported
-}
-
-// CheckProfileAndLoadDefault dummy.
-func CheckProfileAndLoadDefault(name string) (string, error) {
- if name == "" {
- return "", nil
- }
- return "", ErrApparmorUnsupported
-}
-
-// DefaultContent dummy.
-func DefaultContent(name string) ([]byte, error) {
- return nil, nil
-}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 4fe4dd72d..da3755fc8 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -15,7 +15,7 @@ import (
"strings"
"time"
- "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/api/types"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -27,7 +27,7 @@ var (
basePath = &url.URL{
Scheme: "http",
Host: "d",
- Path: "/v" + handlers.MinimalApiVersion + "/libpod",
+ Path: "/v" + types.MinimalAPIVersion + "/libpod",
}
)
@@ -126,7 +126,7 @@ func tcpClient(_url *url.URL) (*http.Client, error) {
return &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
- return net.Dial("tcp", _url.Path)
+ return net.Dial("tcp", _url.Host)
},
DisableCompression: true,
},
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
index 3550c3968..4d8ae6a6e 100644
--- a/pkg/bindings/images/images.go
+++ b/pkg/bindings/images/images.go
@@ -57,7 +57,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit
// Get performs an image inspect. To have the on-disk size of the image calculated, you can
// use the optional size parameter.
-func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageData, error) {
+func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageInspectReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
@@ -66,7 +66,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image
if size != nil {
params.Set("size", strconv.FormatBool(*size))
}
- inspectedData := entities.ImageData{}
+ inspectedData := entities.ImageInspectReport{}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
if err != nil {
return &inspectedData, err
@@ -74,7 +74,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image
return &inspectedData, response.Process(&inspectedData)
}
-func ImageTree(ctx context.Context, nameOrId string) error {
+func Tree(ctx context.Context, nameOrId string) error {
return bindings.ErrNotImplemented
}
@@ -109,23 +109,34 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe
return &report, response.Process(&report)
}
-// Remove deletes an image from local storage. The optional force parameter will forcibly remove
-// the image by removing all all containers, including those that are Running, first.
-func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
- var deletes []map[string]string
+// Remove deletes an image from local storage. The optional force parameter
+// will forcibly remove the image by removing all all containers, including
+// those that are Running, first.
+func Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
+ var report handlers.LibpodImagesRemoveReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
- if force != nil {
- params.Set("force", strconv.FormatBool(*force))
+ params.Set("all", strconv.FormatBool(opts.All))
+ params.Set("force", strconv.FormatBool(opts.Force))
+ for _, i := range images {
+ params.Add("images", i)
}
- response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
+
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/remove", params)
if err != nil {
return nil, err
}
- return deletes, response.Process(&deletes)
+ if err := response.Process(&report); err != nil {
+ return nil, err
+ }
+ var rmError error
+ if report.Error != "" {
+ rmError = errors.New(report.Error)
+ }
+ return &report.ImageRemoveReport, rmError
}
// Export saves an image from local storage as a tarball or image archive. The optional format
@@ -262,9 +273,10 @@ func Pull(ctx context.Context, rawImage string, options entities.ImagePullOption
params.Set("credentials", options.Credentials)
params.Set("overrideArch", options.OverrideArch)
params.Set("overrideOS", options.OverrideOS)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
params.Set("allTags", strconv.FormatBool(options.AllTags))
@@ -299,9 +311,10 @@ func Push(ctx context.Context, source string, destination string, options entiti
params := url.Values{}
params.Set("credentials", options.Credentials)
params.Set("destination", destination)
- if options.TLSVerify != types.OptionalBoolUndefined {
- val := bool(options.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if options.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(options.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
path := fmt.Sprintf("/images/%s/push", source)
@@ -322,9 +335,10 @@ func Search(ctx context.Context, term string, opts entities.ImageSearchOptions)
params.Set("filters", f)
}
- if opts.TLSVerify != types.OptionalBoolUndefined {
- val := bool(opts.TLSVerify == types.OptionalBoolTrue)
- params.Set("tlsVerify", strconv.FormatBool(val))
+ if opts.SkipTLSVerify != types.OptionalBoolUndefined {
+ // Note: we have to verify if skipped is false.
+ verifyTLS := bool(opts.SkipTLSVerify == types.OptionalBoolFalse)
+ params.Set("tlsVerify", strconv.FormatBool(verifyTLS))
}
response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
index 83847614a..b213c8c73 100644
--- a/pkg/bindings/pods/pods.go
+++ b/pkg/bindings/pods/pods.go
@@ -2,6 +2,7 @@ package pods
import (
"context"
+ "errors"
"net/http"
"net/url"
"strconv"
@@ -98,17 +99,19 @@ func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, erro
return &report, response.Process(&report)
}
-// Prune removes all non-running pods in local storage.
-func Prune(ctx context.Context) error {
+// Prune by default removes all non-running pods in local storage.
+// And with force set true removes all pods.
+func Prune(ctx context.Context) ([]*entities.PodPruneReport, error) {
+ var reports []*entities.PodPruneReport
conn, err := bindings.GetClient(ctx)
if err != nil {
- return err
+ return nil, err
}
response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
if err != nil {
- return err
+ return nil, err
}
- return response.Process(nil)
+ return reports, response.Process(&reports)
}
// List returns all pods in local storage. The optional filters parameter can
@@ -187,11 +190,6 @@ func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, erro
return &report, response.Process(&report)
}
-func Stats() error {
- // TODO
- return bindings.ErrNotImplemented
-}
-
// Stop stops all containers in a Pod. The optional timeout parameter can be
// used to override the timeout before the container is killed.
func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStopReport, error) {
@@ -262,3 +260,26 @@ func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport,
}
return &report, response.Process(&report)
}
+
+// Stats display resource-usage statistics of one or more pods.
+func Stats(ctx context.Context, namesOrIDs []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) {
+ if options.Latest {
+ return nil, errors.New("latest is not supported")
+ }
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ for _, i := range namesOrIDs {
+ params.Add("namesOrIDs", i)
+ }
+ params.Set("all", strconv.FormatBool(options.All))
+
+ var reports []*entities.PodStatsReport
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/stats", params)
+ if err != nil {
+ return nil, err
+ }
+ return reports, response.Process(&reports)
+}
diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go
index fce8bbb8e..df6b529de 100644
--- a/pkg/bindings/system/system.go
+++ b/pkg/bindings/system/system.go
@@ -6,9 +6,10 @@ import (
"io"
"net/http"
"net/url"
+ "strconv"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -16,7 +17,7 @@ import (
// Events allows you to monitor libdpod related events like container creation and
// removal. The events are then passed to the eventChan provided. The optional cancelChan
// can be used to cancel the read of events and close down the HTTP connection.
-func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
+func Events(ctx context.Context, eventChan chan (entities.Event), cancelChan chan bool, since, until *string, filters map[string][]string) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
@@ -48,7 +49,7 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha
}
dec := json.NewDecoder(response.Body)
for {
- e := handlers.Event{}
+ e := entities.Event{}
if err := dec.Decode(&e); err != nil {
if err == io.EOF {
break
@@ -59,3 +60,26 @@ func Events(ctx context.Context, eventChan chan (handlers.Event), cancelChan cha
}
return nil
}
+
+// Prune removes all unused system data.
+func Prune(ctx context.Context, all, volumes *bool) (*entities.SystemPruneReport, error) {
+ var (
+ report entities.SystemPruneReport
+ )
+ conn, err := bindings.GetClient(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := url.Values{}
+ if all != nil {
+ params.Set("All", strconv.FormatBool(*all))
+ }
+ if volumes != nil {
+ params.Set("Volumes", strconv.FormatBool(*volumes))
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/system/prune", params)
+ if err != nil {
+ return nil, err
+ }
+ return &report, response.Process(&report)
+}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
index 6b8d6788c..f33e42440 100644
--- a/pkg/bindings/test/common_test.go
+++ b/pkg/bindings/test/common_test.go
@@ -3,13 +3,13 @@ package test_bindings
import (
"context"
"fmt"
- "github.com/containers/libpod/libpod/define"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
+ "github.com/containers/libpod/libpod/define"
. "github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/bindings/containers"
"github.com/containers/libpod/pkg/specgen"
@@ -189,7 +189,7 @@ func (b *bindingTest) restoreImageFromCache(i testImage) {
// Run a container within or without a pod
// and add or append the alpine image to it
func (b *bindingTest) RunTopContainer(containerName *string, insidePod *bool, podName *string) (string, error) {
- s := specgen.NewSpecGenerator(alpine.name)
+ s := specgen.NewSpecGenerator(alpine.name, false)
s.Terminal = false
s.Command = []string{"top"}
if containerName != nil {
diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go
index e288dc368..c79d89b73 100644
--- a/pkg/bindings/test/containers_test.go
+++ b/pkg/bindings/test/containers_test.go
@@ -360,7 +360,7 @@ var _ = Describe("Podman containers ", func() {
It("logging", func() {
stdoutChan := make(chan string, 10)
- s := specgen.NewSpecGenerator(alpine.name)
+ s := specgen.NewSpecGenerator(alpine.name, false)
s.Terminal = true
s.Command = []string{"date", "-R"}
r, err := containers.CreateWithSpec(bt.conn, s)
@@ -521,7 +521,7 @@ var _ = Describe("Podman containers ", func() {
})
It("container init", func() {
- s := specgen.NewSpecGenerator(alpine.name)
+ s := specgen.NewSpecGenerator(alpine.name, false)
ctr, err := containers.CreateWithSpec(bt.conn, s)
Expect(err).To(BeNil())
err = containers.ContainerInit(bt.conn, ctr.ID)
diff --git a/pkg/bindings/test/create_test.go b/pkg/bindings/test/create_test.go
index f83a9b14d..a63aa79cf 100644
--- a/pkg/bindings/test/create_test.go
+++ b/pkg/bindings/test/create_test.go
@@ -31,7 +31,7 @@ var _ = Describe("Create containers ", func() {
})
It("create a container running top", func() {
- s := specgen.NewSpecGenerator(alpine.name)
+ s := specgen.NewSpecGenerator(alpine.name, false)
s.Command = []string{"top"}
s.Terminal = true
s.Name = "top"
diff --git a/pkg/bindings/test/info_test.go b/pkg/bindings/test/info_test.go
index d0e651134..64f2b458f 100644
--- a/pkg/bindings/test/info_test.go
+++ b/pkg/bindings/test/info_test.go
@@ -45,7 +45,7 @@ var _ = Describe("Podman info", func() {
})
It("podman info container counts", func() {
- s := specgen.NewSpecGenerator(alpine.name)
+ s := specgen.NewSpecGenerator(alpine.name, false)
_, err := containers.CreateWithSpec(bt.conn, s)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go
index 579161b26..8a0b9c7a6 100644
--- a/pkg/bindings/test/pods_test.go
+++ b/pkg/bindings/test/pods_test.go
@@ -174,8 +174,7 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
response, err := pods.Inspect(bt.conn, newpod)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.Status).To(Equal(define.PodStatePaused))
+ Expect(response.State).To(Equal(define.PodStatePaused))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStatePaused))
@@ -186,8 +185,7 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
response, err = pods.Inspect(bt.conn, newpod)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ Expect(response.State).To(Equal(define.PodStateRunning))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateRunning))
@@ -219,8 +217,7 @@ var _ = Describe("Podman pods", func() {
response, err := pods.Inspect(bt.conn, newpod)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ Expect(response.State).To(Equal(define.PodStateRunning))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateRunning))
@@ -234,8 +231,7 @@ var _ = Describe("Podman pods", func() {
_, err = pods.Stop(bt.conn, newpod, nil)
Expect(err).To(BeNil())
response, _ = pods.Inspect(bt.conn, newpod)
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateExited))
+ Expect(response.State).To(Equal(define.PodStateExited))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateStopped))
@@ -248,8 +244,7 @@ var _ = Describe("Podman pods", func() {
_, err = pods.Restart(bt.conn, newpod)
Expect(err).To(BeNil())
response, _ = pods.Inspect(bt.conn, newpod)
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateRunning))
+ Expect(response.State).To(Equal(define.PodStateRunning))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateRunning))
@@ -262,7 +257,7 @@ var _ = Describe("Podman pods", func() {
var newpod2 string = "newpod2"
bt.Podcreate(&newpod2)
// No pods pruned since no pod in exited state
- err = pods.Prune(bt.conn)
+ pruneResponse, err := pods.Prune(bt.conn)
Expect(err).To(BeNil())
podSummary, err := pods.List(bt.conn, nil)
Expect(err).To(BeNil())
@@ -277,15 +272,21 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
response, err := pods.Inspect(bt.conn, newpod)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateExited))
- err = pods.Prune(bt.conn)
+ Expect(response.State).To(Equal(define.PodStateExited))
+ pruneResponse, err = pods.Prune(bt.conn)
Expect(err).To(BeNil())
+ // Validate status and record pod id of pod to be pruned
+ Expect(response.State).To(Equal(define.PodStateExited))
+ podID := response.ID
+ // Check if right pod was pruned
+ Expect(len(pruneResponse)).To(Equal(1))
+ Expect(pruneResponse[0].Id).To(Equal(podID))
+ // One pod is pruned hence only one pod should be active.
podSummary, err = pods.List(bt.conn, nil)
Expect(err).To(BeNil())
Expect(len(podSummary)).To(Equal(1))
- // Test prune all pods in exited state.
+ // Test prune multiple pods.
bt.Podcreate(&newpod)
_, err = pods.Start(bt.conn, newpod)
Expect(err).To(BeNil())
@@ -295,8 +296,7 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
response, err = pods.Inspect(bt.conn, newpod)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateExited))
+ Expect(response.State).To(Equal(define.PodStateExited))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateStopped))
@@ -305,13 +305,12 @@ var _ = Describe("Podman pods", func() {
Expect(err).To(BeNil())
response, err = pods.Inspect(bt.conn, newpod2)
Expect(err).To(BeNil())
- // FIXME sujil please fix this
- //Expect(response.State.Status).To(Equal(define.PodStateExited))
+ Expect(response.State).To(Equal(define.PodStateExited))
for _, i := range response.Containers {
Expect(define.StringToContainerStatus(i.State)).
To(Equal(define.ContainerStateStopped))
}
- err = pods.Prune(bt.conn)
+ _, err = pods.Prune(bt.conn)
Expect(err).To(BeNil())
podSummary, err = pods.List(bt.conn, nil)
Expect(err).To(BeNil())
diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go
index 3abc26b34..87e6d56dc 100644
--- a/pkg/bindings/test/system_test.go
+++ b/pkg/bindings/test/system_test.go
@@ -4,7 +4,12 @@ import (
"time"
"github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/containers"
+ "github.com/containers/libpod/pkg/bindings/pods"
"github.com/containers/libpod/pkg/bindings/system"
+ "github.com/containers/libpod/pkg/bindings/volumes"
+ "github.com/containers/libpod/pkg/domain/entities"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
@@ -12,13 +17,16 @@ import (
var _ = Describe("Podman system", func() {
var (
- bt *bindingTest
- s *gexec.Session
+ bt *bindingTest
+ s *gexec.Session
+ newpod string
)
BeforeEach(func() {
bt = newBindingTest()
bt.RestoreImagesFromCache()
+ newpod = "newpod"
+ bt.Podcreate(&newpod)
s = bt.startAPIService()
time.Sleep(1 * time.Second)
err := bt.NewConnection()
@@ -48,4 +56,98 @@ var _ = Describe("Podman system", func() {
cancelChan <- true
Expect(len(messages)).To(BeNumerically("==", 3))
})
+
+ It("podman system prune - pod,container stopped", func() {
+ // Start and stop a pod to enter in exited state.
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ To(ContainElement("docker.io/library/alpine:latest"))
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0))
+ })
+
+ It("podman system prune running alpine container", func() {
+ // Start and stop a pod to enter in exited state.
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+ _, err = pods.Stop(bt.conn, newpod, nil)
+ Expect(err).To(BeNil())
+
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // Start container and leave in running
+ var name2 = "top2"
+ _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ // Adding an unused volume
+ _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PFalse)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(1))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ // Alpine image should not be pruned as used by running container
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ ToNot(ContainElement("docker.io/library/alpine:latest"))
+ // Though unsed volume is available it should not be pruned as flag set to false.
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(0))
+ })
+
+ It("podman system prune running alpine container volume prune", func() {
+ // Start a pod and leave it running
+ _, err := pods.Start(bt.conn, newpod)
+ Expect(err).To(BeNil())
+
+ // Start and stop a container to enter in exited state.
+ var name = "top"
+ _, err = bt.RunTopContainer(&name, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+ err = containers.Stop(bt.conn, name, nil)
+ Expect(err).To(BeNil())
+
+ // Start second container and leave in running
+ var name2 = "top2"
+ _, err = bt.RunTopContainer(&name2, &bindings.PFalse, nil)
+ Expect(err).To(BeNil())
+
+ // Adding an unused volume should work
+ _, err = volumes.Create(bt.conn, entities.VolumeCreateOptions{})
+ Expect(err).To(BeNil())
+
+ systemPruneResponse, err := system.Prune(bt.conn, &bindings.PTrue, &bindings.PTrue)
+ Expect(err).To(BeNil())
+ Expect(len(systemPruneResponse.PodPruneReport)).To(Equal(0))
+ Expect(len(systemPruneResponse.ContainerPruneReport.ID)).To(Equal(1))
+ Expect(len(systemPruneResponse.ImagePruneReport.Report.Id)).
+ To(BeNumerically(">", 0))
+ // Alpine image should not be pruned as used by running container
+ Expect(systemPruneResponse.ImagePruneReport.Report.Id).
+ ToNot(ContainElement("docker.io/library/alpine:latest"))
+ // Volume should be pruned now as flag set true
+ Expect(len(systemPruneResponse.VolumePruneReport)).To(Equal(1))
+ })
})
diff --git a/pkg/domain/entities/container_ps.go b/pkg/domain/entities/container_ps.go
index 709bb58d6..fd94d93be 100644
--- a/pkg/domain/entities/container_ps.go
+++ b/pkg/domain/entities/container_ps.go
@@ -25,6 +25,8 @@ type ListContainer struct {
ID string `json:"Id"`
// Container image
Image string
+ // Container image ID
+ ImageID string
// If this container is a Pod infra container
IsInfra bool
// Labels for container
@@ -159,3 +161,31 @@ func SortPsOutput(sortBy string, psOutput SortListContainers) (SortListContainer
}
return psOutput, nil
}
+
+func (l ListContainer) CGROUPNS() string {
+ return l.Namespaces.Cgroup
+}
+
+func (l ListContainer) IPC() string {
+ return l.Namespaces.IPC
+}
+
+func (l ListContainer) MNT() string {
+ return l.Namespaces.MNT
+}
+
+func (l ListContainer) NET() string {
+ return l.Namespaces.NET
+}
+
+func (l ListContainer) PIDNS() string {
+ return l.Namespaces.PIDNS
+}
+
+func (l ListContainer) USERNS() string {
+ return l.Namespaces.User
+}
+
+func (l ListContainer) UTS() string {
+ return l.Namespaces.UTS
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 52327a905..e58258b75 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -8,6 +8,7 @@ import (
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/cri-o/ocicni/pkg/ocicni"
)
type WaitOptions struct {
@@ -341,3 +342,27 @@ type ContainerPruneReport struct {
ID map[string]int64
Err map[string]error
}
+
+// ContainerPortOptions describes the options to obtain
+// port information on containers
+type ContainerPortOptions struct {
+ All bool
+ Latest bool
+}
+
+// ContainerPortReport describes the output needed for
+// the CLI to output ports
+type ContainerPortReport struct {
+ Id string
+ Ports []ocicni.PortMapping
+}
+
+// ContainerCpOptions describes input options for cp
+type ContainerCpOptions struct {
+ Pause bool
+ Extract bool
+}
+
+// ContainerCpReport describes the output from a cp operation
+type ContainerCpReport struct {
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 02938413a..eebf4c033 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -14,8 +14,8 @@ type ContainerEngine interface {
ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error
ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error)
ContainerCleanup(ctx context.Context, namesOrIds []string, options ContainerCleanupOptions) ([]*ContainerCleanupReport, error)
- ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error)
ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error)
+ ContainerCp(ctx context.Context, source, dest string, options ContainerCpOptions) (*ContainerCpReport, error)
ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error)
ContainerDiff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error)
@@ -28,6 +28,8 @@ type ContainerEngine interface {
ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error
ContainerMount(ctx context.Context, nameOrIds []string, options ContainerMountOptions) ([]*ContainerMountReport, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
+ ContainerPort(ctx context.Context, nameOrId string, options ContainerPortOptions) ([]*ContainerPortReport, error)
+ ContainerPrune(ctx context.Context, options ContainerPruneOptions) (*ContainerPruneReport, error)
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
@@ -39,6 +41,7 @@ type ContainerEngine interface {
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
+ GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
@@ -46,15 +49,17 @@ type ContainerEngine interface {
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error)
+ PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error)
PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error)
PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error)
PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error)
PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error)
+ PodStats(ctx context.Context, namesOrIds []string, options PodStatsOptions) ([]*PodStatsReport, error)
PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error)
PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error)
PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error)
- RestService(ctx context.Context, opts ServiceOptions) error
SetupRootless(ctx context.Context, cmd *cobra.Command) error
+ Shutdown(ctx context.Context)
VarlinkService(ctx context.Context, opts ServiceOptions) error
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error)
VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error)
diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go
index e3b606550..46a96ca20 100644
--- a/pkg/domain/entities/engine_image.go
+++ b/pkg/domain/entities/engine_image.go
@@ -7,20 +7,26 @@ import (
)
type ImageEngine interface {
+ Build(ctx context.Context, containerFiles []string, opts BuildOptions) (*BuildReport, error)
Config(ctx context.Context) (*config.Config, error)
- Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error)
Diff(ctx context.Context, nameOrId string, options DiffOptions) (*DiffReport, error)
Exists(ctx context.Context, nameOrId string) (*BoolReport, error)
History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error)
Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error)
- Inspect(ctx context.Context, names []string, opts InspectOptions) (*ImageInspectReport, error)
+ Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, error)
List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error)
Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error)
Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error)
Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error
+ Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, error)
Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error
Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error)
+ Shutdown(ctx context.Context)
Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error
+ Tree(ctx context.Context, nameOrId string, options ImageTreeOptions) (*ImageTreeReport, error)
Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error
+ ManifestCreate(ctx context.Context, names, images []string, opts ManifestCreateOptions) (string, error)
+ ManifestInspect(ctx context.Context, name string) ([]byte, error)
+ ManifestAdd(ctx context.Context, opts ManifestAddOptions) (string, error)
}
diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go
new file mode 100644
index 000000000..8861be158
--- /dev/null
+++ b/pkg/domain/entities/events.go
@@ -0,0 +1,61 @@
+package entities
+
+import (
+ "strconv"
+ "time"
+
+ libpodEvents "github.com/containers/libpod/libpod/events"
+ dockerEvents "github.com/docker/docker/api/types/events"
+)
+
+// Event combines various event-related data such as time, event type, status
+// and more.
+type Event struct {
+ // TODO: it would be nice to have full control over the types at some
+ // point and fork such Docker types.
+ dockerEvents.Message
+}
+
+// ConvertToLibpodEvent converts an entities event to a libpod one.
+func ConvertToLibpodEvent(e Event) *libpodEvents.Event {
+ exitCode, err := strconv.Atoi(e.Actor.Attributes["containerExitCode"])
+ if err != nil {
+ return nil
+ }
+ status, err := libpodEvents.StringToStatus(e.Action)
+ if err != nil {
+ return nil
+ }
+ t, err := libpodEvents.StringToType(e.Type)
+ if err != nil {
+ return nil
+ }
+ return &libpodEvents.Event{
+ ContainerExitCode: exitCode,
+ ID: e.Actor.ID,
+ Image: e.Actor.Attributes["image"],
+ Name: e.Actor.Attributes["name"],
+ Status: status,
+ Time: time.Unix(e.Time, e.TimeNano),
+ Type: t,
+ }
+}
+
+// ConvertToEntitiesEvent converts a libpod event to an entities one.
+func ConvertToEntitiesEvent(e libpodEvents.Event) *Event {
+ return &Event{dockerEvents.Message{
+ Type: e.Type.String(),
+ Action: e.Status.String(),
+ Actor: dockerEvents.Actor{
+ ID: e.ID,
+ Attributes: map[string]string{
+ "image": e.Image,
+ "name": e.Name,
+ "containerExitCode": strconv.Itoa(e.ContainerExitCode),
+ },
+ },
+ Scope: "local",
+ Time: e.Time.Unix(),
+ TimeNano: e.Time.UnixNano(),
+ }}
+}
diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go
new file mode 100644
index 000000000..6d65b52f8
--- /dev/null
+++ b/pkg/domain/entities/generate.go
@@ -0,0 +1,22 @@
+package entities
+
+// GenerateSystemdOptions control the generation of systemd unit files.
+type GenerateSystemdOptions struct {
+ // Files - generate files instead of printing to stdout.
+ Files bool
+ // Name - use container/pod name instead of its ID.
+ Name bool
+ // New - create a new container instead of starting a new one.
+ New bool
+ // RestartPolicy - systemd restart policy.
+ RestartPolicy string
+ // StopTimeout - time when stopping the container.
+ StopTimeout *uint
+}
+
+// GenerateSystemdReport
+type GenerateSystemdReport struct {
+ // Output of the generate process. Either the generated files or their
+ // entire content.
+ Output string
+}
diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go
index 78ebb8805..74f27e25f 100644
--- a/pkg/domain/entities/images.go
+++ b/pkg/domain/entities/images.go
@@ -50,10 +50,10 @@ func (i *Image) Id() string {
}
type ImageSummary struct {
- ID string `json:"Id"`
+ ID string
ParentId string `json:",omitempty"`
RepoTags []string `json:",omitempty"`
- Created int64 `json:",omitempty"`
+ Created time.Time `json:",omitempty"`
Size int64 `json:",omitempty"`
SharedSize int `json:",omitempty"`
VirtualSize int64 `json:",omitempty"`
@@ -82,19 +82,24 @@ func (i *ImageSummary) IsDangling() bool {
return i.Dangling
}
-type ImageDeleteOptions struct {
- All bool
+// ImageRemoveOptions can be used to alter image removal.
+type ImageRemoveOptions struct {
+ // All will remove all images.
+ All bool
+ // Foce will force image removal including containers using the images.
Force bool
}
-// ImageDeleteResponse is the response for removing one or more image(s) from storage
-// and containers what was untagged vs actually removed
-type ImageDeleteReport struct {
- Untagged []string `json:",omitempty"`
- Deleted []string `json:",omitempty"`
- Errors []error
- ImageNotFound error
- ImageInUse error
+// ImageRemoveResponse is the response for removing one or more image(s) from storage
+// and containers what was untagged vs actually removed.
+type ImageRemoveReport struct {
+ // Deleted images.
+ Deleted []string `json:",omitempty"`
+ // Untagged images. Can be longer than Deleted.
+ Untagged []string `json:",omitempty"`
+ // ExitCode describes the exit codes as described in the `podman rmi`
+ // man page.
+ ExitCode int
}
type ImageHistoryOptions struct{}
@@ -136,8 +141,8 @@ type ImagePullOptions struct {
Quiet bool
// SignaturePolicy to use when pulling. Ignored for remote calls.
SignaturePolicy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImagePullReport is the response from pulling one or more images.
@@ -178,8 +183,8 @@ type ImagePushOptions struct {
// SignBy adds a signature at the destination using the specified key.
// Ignored for remote calls.
SignBy string
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchOptions are the arguments for searching images.
@@ -193,8 +198,8 @@ type ImageSearchOptions struct {
Limit int
// NoTrunc will not truncate the output.
NoTrunc bool
- // TLSVerify to enable/disable HTTPS and certificate verification.
- TLSVerify types.OptionalBool
+ // SkipTLSVerify to skip HTTPS and certificate verification.
+ SkipTLSVerify types.OptionalBool
}
// ImageSearchReport is the response from searching images.
@@ -213,6 +218,7 @@ type ImageSearchReport struct {
Automated string
}
+// Image List Options
type ImageListOptions struct {
All bool `json:"all" schema:"all"`
Filter []string `json:"Filter,omitempty"`
@@ -233,13 +239,9 @@ type ImagePruneReport struct {
type ImageTagOptions struct{}
type ImageUntagOptions struct{}
-type ImageData struct {
- *inspect.ImageData
-}
-
+// ImageInspectReport is the data when inspecting an image.
type ImageInspectReport struct {
- Images []*ImageData
- Errors map[string]error
+ *inspect.ImageData
}
type ImageLoadOptions struct {
@@ -251,7 +253,7 @@ type ImageLoadOptions struct {
}
type ImageLoadReport struct {
- Name string
+ Names []string
}
type ImageImportOptions struct {
@@ -273,3 +275,13 @@ type ImageSaveOptions struct {
Output string
Quiet bool
}
+
+// ImageTreeOptions provides options for ImageEngine.Tree()
+type ImageTreeOptions struct {
+ WhatRequires bool // Show all child images and layers of the specified image
+}
+
+// ImageTreeReport provides results from ImageEngine.Tree()
+type ImageTreeReport struct {
+ Tree string // TODO: Refactor move presentation work out of server
+}
diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go
new file mode 100644
index 000000000..7316735b0
--- /dev/null
+++ b/pkg/domain/entities/manifest.go
@@ -0,0 +1,16 @@
+package entities
+
+type ManifestCreateOptions struct {
+ All bool `schema:"all"`
+}
+
+type ManifestAddOptions struct {
+ All bool `json:"all" schema:"all"`
+ Annotation []string `json:"annotation" schema:"annotation"`
+ Arch string `json:"arch" schema:"arch"`
+ Features []string `json:"features" schema:"features"`
+ Images []string `json:"images" schema:"images"`
+ OS string `json:"os" schema:"os"`
+ OSVersion string `json:"os_version" schema:"os_version"`
+ Variant string `json:"variant" schema:"variant"`
+}
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index b280203de..a4896ce4d 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -1,6 +1,7 @@
package entities
import (
+ "errors"
"strings"
"time"
@@ -134,7 +135,7 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
s.StaticMAC = p.Net.StaticMAC
s.PortMappings = p.Net.PublishPorts
s.CNINetworks = p.Net.CNINetworks
- if p.Net.DNSHost {
+ if p.Net.UseImageResolvConf {
s.NoManageResolvConf = true
}
s.DNSServer = p.Net.DNSServers
@@ -147,6 +148,15 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
s.CgroupParent = p.CGroupParent
}
+type PodPruneOptions struct {
+ Force bool `json:"force" schema:"force"`
+}
+
+type PodPruneReport struct {
+ Err error
+ Id string
+}
+
type PodTopOptions struct {
// CLI flags.
ListDescriptors bool
@@ -179,3 +189,50 @@ type PodInspectOptions struct {
type PodInspectReport struct {
*define.InspectPodData
}
+
+// PodStatsOptions are options for the pod stats command.
+type PodStatsOptions struct {
+ // All - provide stats for all running pods.
+ All bool
+ // Latest - provide stats for the latest pod.
+ Latest bool
+}
+
+// PodStatsReport includes pod-resource statistics data.
+type PodStatsReport struct {
+ CPU string
+ MemUsage string
+ Mem string
+ NetIO string
+ BlockIO string
+ PIDS string
+ Pod string
+ CID string
+ Name string
+}
+
+// ValidatePodStatsOptions validates the specified slice and options. Allows
+// for sharing code in the front- and the back-end.
+func ValidatePodStatsOptions(args []string, options *PodStatsOptions) error {
+ num := 0
+ if len(args) > 0 {
+ num++
+ }
+ if options.All {
+ num++
+ }
+ if options.Latest {
+ num++
+ }
+ switch num {
+ case 0:
+ // Podman v1 compat: if nothing's specified get all running
+ // pods.
+ options.All = true
+ return nil
+ case 1:
+ return nil
+ default:
+ return errors.New("--all, --latest and arguments cannot be used together")
+ }
+}
diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go
index 3ddc04293..de93a382f 100644
--- a/pkg/domain/entities/system.go
+++ b/pkg/domain/entities/system.go
@@ -12,3 +12,17 @@ type ServiceOptions struct {
Timeout time.Duration // duration of inactivity the service should wait before shutting down
Command *cobra.Command // CLI command provided. Used in V1 code
}
+
+// SystemPruneOptions provides options to prune system.
+type SystemPruneOptions struct {
+ All bool
+ Volume bool
+}
+
+// SystemPruneReport provides report after system prune is executed.
+type SystemPruneReport struct {
+ PodPruneReport []*PodPruneReport
+ *ContainerPruneReport
+ *ImagePruneReport
+ VolumePruneReport []*VolumePruneReport
+}
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 31a05f5d3..9fbe04c9a 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -4,6 +4,7 @@ import (
"errors"
"net"
+ "github.com/containers/buildah/imagebuildah"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage/pkg/archive"
@@ -24,33 +25,36 @@ type Report struct {
}
type PodDeleteReport struct{ Report }
-type PodPruneOptions struct{}
-type PodPruneReport struct{ Report }
type VolumeDeleteOptions struct{}
type VolumeDeleteReport struct{ Report }
// NetOptions reflect the shared network options between
// pods and containers
type NetOptions struct {
- AddHosts []string
- CNINetworks []string
- DNSHost bool
- DNSOptions []string
- DNSSearch []string
- DNSServers []net.IP
- Network specgen.Namespace
- NoHosts bool
- PublishPorts []ocicni.PortMapping
- StaticIP *net.IP
- StaticMAC *net.HardwareAddr
+ AddHosts []string
+ CNINetworks []string
+ UseImageResolvConf bool
+ DNSOptions []string
+ DNSSearch []string
+ DNSServers []net.IP
+ Network specgen.Namespace
+ NoHosts bool
+ PublishPorts []ocicni.PortMapping
+ StaticIP *net.IP
+ StaticMAC *net.HardwareAddr
}
// All CLI inspect commands and inspect sub-commands use the same options
type InspectOptions struct {
+ // Format - change the output to JSON or a Go template.
Format string `json:",omitempty"`
- Latest bool `json:",omitempty"`
- Size bool `json:",omitempty"`
+ // Latest - inspect the latest container Podman is aware of.
+ Latest bool `json:",omitempty"`
+ // Size (containers only) - display total file size.
+ Size bool `json:",omitempty"`
+ // Type -- return JSON for specified type.
+ Type string `json:",omitempty"`
}
// All API and CLI diff commands and diff sub-commands use the same options
@@ -104,3 +108,14 @@ func (e ErrorModel) Cause() error {
func (e ErrorModel) Code() int {
return e.ResponseCode
}
+
+// BuildOptions describe the options for building container images.
+type BuildOptions struct {
+ imagebuildah.BuildOptions
+}
+
+// BuildReport is the image-build report.
+type BuildReport struct {
+ // ID of the image.
+ ID string
+}
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index c9df72f2d..82bf82bf0 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
@@ -219,12 +217,23 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin
}
func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, options entities.RestartOptions) ([]*entities.RestartReport, error) {
var (
+ ctrs []*libpod.Container
+ err error
reports []*entities.RestartReport
)
- ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
- if err != nil {
- return nil, err
+
+ if options.Running {
+ ctrs, err = ic.Libpod.GetRunningContainers()
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ ctrs, err = getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
+ if err != nil {
+ return nil, err
+ }
}
+
for _, con := range ctrs {
timeout := con.StopTimeout()
if options.Timeout != nil {
@@ -483,7 +492,7 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil {
return nil, err
}
- ctr, err := generate.MakeContainer(ic.Libpod, s)
+ ctr, err := generate.MakeContainer(ctx, ic.Libpod, s)
if err != nil {
return nil, err
}
@@ -505,7 +514,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string,
}
// If the container is in a pod, also set to recursively start dependencies
- if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
+ if err := terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
@@ -671,7 +680,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
if err := generate.CompleteSpec(ctx, ic.Libpod, opts.Spec); err != nil {
return nil, err
}
- ctr, err := generate.MakeContainer(ic.Libpod, opts.Spec)
+ ctr, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec)
if err != nil {
return nil, err
}
@@ -736,6 +745,16 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta
} else {
report.ExitCode = int(ecode)
}
+ if opts.Rm {
+ if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true); err != nil {
+ if errors.Cause(err) == define.ErrNoSuchCtr ||
+ errors.Cause(err) == define.ErrCtrRemoved {
+ logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err)
+ } else {
+ logrus.Errorf("Error removing container %s: %v", ctr.ID(), err)
+ }
+ }
+ }
return &report, nil
}
@@ -829,7 +848,13 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin
}
for _, ctr := range ctrs {
report := entities.ContainerInitReport{Id: ctr.ID()}
- report.Err = ctr.Init(ctx)
+ err := ctr.Init(ctx)
+
+ // If we're initializing all containers, ignore invalid state errors
+ if options.All && errors.Cause(err) == define.ErrCtrStateInvalid {
+ err = nil
+ }
+ report.Err = err
reports = append(reports, &report)
}
return reports, nil
@@ -921,3 +946,38 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str
func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) {
return ic.Libpod.GetConfig()
}
+
+func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) {
+ var reports []*entities.ContainerPortReport
+ ctrs, err := getContainersByContext(options.All, options.Latest, []string{nameOrId}, ic.Libpod)
+ if err != nil {
+ return nil, err
+ }
+ for _, con := range ctrs {
+ state, err := con.State()
+ if err != nil {
+ return nil, err
+ }
+ if state != define.ContainerStateRunning {
+ continue
+ }
+ portmappings, err := con.PortMappings()
+ if err != nil {
+ return nil, err
+ }
+ if len(portmappings) > 0 {
+ reports = append(reports, &entities.ContainerPortReport{
+ Id: con.ID(),
+ Ports: portmappings,
+ })
+ }
+ }
+ return reports, nil
+}
+
+// Shutdown Libpod engine
+func (ic *ContainerEngine) Shutdown(_ context.Context) {
+ shutdownSync.Do(func() {
+ _ = ic.Libpod.Shutdown(false)
+ })
+}
diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go
new file mode 100644
index 000000000..9fc1e3bee
--- /dev/null
+++ b/pkg/domain/infra/abi/cp.go
@@ -0,0 +1,433 @@
+package abi
+
+import (
+ "archive/tar"
+ "context"
+ "fmt"
+ "io"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/chrootuser"
+ "github.com/containers/buildah/util"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/storage"
+ "github.com/containers/storage/pkg/chrootarchive"
+ "github.com/containers/storage/pkg/idtools"
+ securejoin "github.com/cyphar/filepath-securejoin"
+ "github.com/docker/docker/pkg/archive"
+ "github.com/opencontainers/go-digest"
+ "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
+ var extract bool
+
+ srcCtr, srcPath := parsePath(ic.Libpod, source)
+ destCtr, destPath := parsePath(ic.Libpod, dest)
+
+ if (srcCtr == nil && destCtr == nil) || (srcCtr != nil && destCtr != nil) {
+ return nil, errors.Errorf("invalid arguments %s, %s you must use just one container", source, dest)
+ }
+
+ if len(srcPath) == 0 || len(destPath) == 0 {
+ return nil, errors.Errorf("invalid arguments %s, %s you must specify paths", source, dest)
+ }
+ ctr := srcCtr
+ isFromHostToCtr := ctr == nil
+ if isFromHostToCtr {
+ ctr = destCtr
+ }
+
+ mountPoint, err := ctr.Mount()
+ if err != nil {
+ return nil, err
+ }
+ defer func() {
+ if err := ctr.Unmount(false); err != nil {
+ logrus.Errorf("unable to umount container '%s': %q", ctr.ID(), err)
+ }
+ }()
+
+ if options.Pause {
+ if err := ctr.Pause(); err != nil {
+ // An invalid state error is fine.
+ // The container isn't running or is already paused.
+ // TODO: We can potentially start the container while
+ // the copy is running, which still allows a race where
+ // malicious code could mess with the symlink.
+ if errors.Cause(err) != define.ErrCtrStateInvalid {
+ return nil, err
+ }
+ } else {
+ // Only add the defer if we actually paused
+ defer func() {
+ if err := ctr.Unpause(); err != nil {
+ logrus.Errorf("Error unpausing container after copying: %v", err)
+ }
+ }()
+ }
+ }
+
+ user, err := getUser(mountPoint, ctr.User())
+ if err != nil {
+ return nil, err
+ }
+ idMappingOpts, err := ctr.IDMappings()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting IDMappingOptions")
+ }
+ destOwner := idtools.IDPair{UID: int(user.UID), GID: int(user.GID)}
+ hostUID, hostGID, err := util.GetHostIDs(convertIDMap(idMappingOpts.UIDMap), convertIDMap(idMappingOpts.GIDMap), user.UID, user.GID)
+ if err != nil {
+ return nil, err
+ }
+
+ hostOwner := idtools.IDPair{UID: int(hostUID), GID: int(hostGID)}
+
+ if isFromHostToCtr {
+ if isVol, volDestName, volName := isVolumeDestName(destPath, ctr); isVol { //nolint(gocritic)
+ path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, destPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting destination path from volume %s", volDestName)
+ }
+ destPath = path
+ } else if isBindMount, mount := isBindMountDestName(destPath, ctr); isBindMount { //nolint(gocritic)
+ path, err := pathWithBindMountSource(mount, destPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting destination path from bind mount %s", mount.Destination)
+ }
+ destPath = path
+ } else if filepath.IsAbs(destPath) { //nolint(gocritic)
+ cleanedPath, err := securejoin.SecureJoin(mountPoint, destPath)
+ if err != nil {
+ return nil, err
+ }
+ destPath = cleanedPath
+ } else { //nolint(gocritic)
+ ctrWorkDir, err := securejoin.SecureJoin(mountPoint, ctr.WorkingDir())
+ if err != nil {
+ return nil, err
+ }
+ if err = idtools.MkdirAllAndChownNew(ctrWorkDir, 0755, hostOwner); err != nil {
+ return nil, errors.Wrapf(err, "error creating directory %q", destPath)
+ }
+ cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), destPath))
+ if err != nil {
+ return nil, err
+ }
+ destPath = cleanedPath
+ }
+ } else {
+ destOwner = idtools.IDPair{UID: os.Getuid(), GID: os.Getgid()}
+ if isVol, volDestName, volName := isVolumeDestName(srcPath, ctr); isVol { //nolint(gocritic)
+ path, err := pathWithVolumeMount(ctr, ic.Libpod, volDestName, volName, srcPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting source path from volume %s", volDestName)
+ }
+ srcPath = path
+ } else if isBindMount, mount := isBindMountDestName(srcPath, ctr); isBindMount { //nolint(gocritic)
+ path, err := pathWithBindMountSource(mount, srcPath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting source path from bind mount %s", mount.Destination)
+ }
+ srcPath = path
+ } else if filepath.IsAbs(srcPath) { //nolint(gocritic)
+ cleanedPath, err := securejoin.SecureJoin(mountPoint, srcPath)
+ if err != nil {
+ return nil, err
+ }
+ srcPath = cleanedPath
+ } else { //nolint(gocritic)
+ cleanedPath, err := securejoin.SecureJoin(mountPoint, filepath.Join(ctr.WorkingDir(), srcPath))
+ if err != nil {
+ return nil, err
+ }
+ srcPath = cleanedPath
+ }
+ }
+
+ if !filepath.IsAbs(destPath) {
+ dir, err := os.Getwd()
+ if err != nil {
+ return nil, errors.Wrapf(err, "err getting current working directory")
+ }
+ destPath = filepath.Join(dir, destPath)
+ }
+
+ if source == "-" {
+ srcPath = os.Stdin.Name()
+ extract = true
+ }
+ err = containerCopy(srcPath, destPath, source, dest, idMappingOpts, &destOwner, extract, isFromHostToCtr)
+ return &entities.ContainerCpReport{}, err
+}
+
+func getUser(mountPoint string, userspec string) (specs.User, error) {
+ uid, gid, _, err := chrootuser.GetUser(mountPoint, userspec)
+ u := specs.User{
+ UID: uid,
+ GID: gid,
+ Username: userspec,
+ }
+ if !strings.Contains(userspec, ":") {
+ groups, err2 := chrootuser.GetAdditionalGroupsForUser(mountPoint, uint64(u.UID))
+ if err2 != nil {
+ if errors.Cause(err2) != chrootuser.ErrNoSuchUser && err == nil {
+ err = err2
+ }
+ } else {
+ u.AdditionalGids = groups
+ }
+
+ }
+ return u, err
+}
+
+func parsePath(runtime *libpod.Runtime, path string) (*libpod.Container, string) {
+ pathArr := strings.SplitN(path, ":", 2)
+ if len(pathArr) == 2 {
+ ctr, err := runtime.LookupContainer(pathArr[0])
+ if err == nil {
+ return ctr, pathArr[1]
+ }
+ }
+ return nil, path
+}
+
+func evalSymlinks(path string) (string, error) {
+ if path == os.Stdin.Name() {
+ return path, nil
+ }
+ return filepath.EvalSymlinks(path)
+}
+
+func getPathInfo(path string) (string, os.FileInfo, error) {
+ path, err := evalSymlinks(path)
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error evaluating symlinks %q", path)
+ }
+ srcfi, err := os.Stat(path)
+ if err != nil {
+ return "", nil, errors.Wrapf(err, "error reading path %q", path)
+ }
+ return path, srcfi, nil
+}
+
+func containerCopy(srcPath, destPath, src, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract, isFromHostToCtr bool) error {
+ srcPath, err := evalSymlinks(srcPath)
+ if err != nil {
+ return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
+ }
+
+ srcPath, srcfi, err := getPathInfo(srcPath)
+ if err != nil {
+ return err
+ }
+
+ filename := filepath.Base(destPath)
+ if filename == "-" && !isFromHostToCtr {
+ err := streamFileToStdout(srcPath, srcfi)
+ if err != nil {
+ return errors.Wrapf(err, "error streaming source file %s to Stdout", srcPath)
+ }
+ return nil
+ }
+
+ destdir := destPath
+ if !srcfi.IsDir() {
+ destdir = filepath.Dir(destPath)
+ }
+ _, err = os.Stat(destdir)
+ if err != nil && !os.IsNotExist(err) {
+ return errors.Wrapf(err, "error checking directory %q", destdir)
+ }
+ destDirIsExist := err == nil
+ if err = os.MkdirAll(destdir, 0755); err != nil {
+ return errors.Wrapf(err, "error creating directory %q", destdir)
+ }
+
+ // return functions for copying items
+ copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+ copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+ untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+
+ if srcfi.IsDir() {
+ logrus.Debugf("copying %q to %q", srcPath+string(os.PathSeparator)+"*", dest+string(os.PathSeparator)+"*")
+ if destDirIsExist && !strings.HasSuffix(src, fmt.Sprintf("%s.", string(os.PathSeparator))) {
+ destPath = filepath.Join(destPath, filepath.Base(srcPath))
+ }
+ if err = copyWithTar(srcPath, destPath); err != nil {
+ return errors.Wrapf(err, "error copying %q to %q", srcPath, dest)
+ }
+ return nil
+ }
+
+ 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
+ }
+
+ destfi, err := os.Stat(destPath)
+ if err != nil {
+ if !os.IsNotExist(err) || strings.HasSuffix(dest, string(os.PathSeparator)) {
+ return errors.Wrapf(err, "failed to get stat of dest path %s", destPath)
+ }
+ }
+ 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)
+ }
+ return nil
+}
+
+func convertIDMap(idMaps []idtools.IDMap) (convertedIDMap []specs.LinuxIDMapping) {
+ for _, idmap := range idMaps {
+ tempIDMap := specs.LinuxIDMapping{
+ ContainerID: uint32(idmap.ContainerID),
+ HostID: uint32(idmap.HostID),
+ Size: uint32(idmap.Size),
+ }
+ convertedIDMap = append(convertedIDMap, tempIDMap)
+ }
+ return convertedIDMap
+}
+
+func streamFileToStdout(srcPath string, srcfi os.FileInfo) error {
+ if srcfi.IsDir() {
+ tw := tar.NewWriter(os.Stdout)
+ err := filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
+ if err != nil || !info.Mode().IsRegular() || path == srcPath {
+ return err
+ }
+ hdr, err := tar.FileInfoHeader(info, "")
+ if err != nil {
+ return err
+ }
+
+ if err = tw.WriteHeader(hdr); err != nil {
+ return err
+ }
+ fh, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer fh.Close()
+
+ _, err = io.Copy(tw, fh)
+ return err
+ })
+ if err != nil {
+ return errors.Wrapf(err, "error streaming directory %s to Stdout", srcPath)
+ }
+ return nil
+ }
+
+ file, err := os.Open(srcPath)
+ if err != nil {
+ return errors.Wrapf(err, "error opening file %s", srcPath)
+ }
+ defer file.Close()
+ if !archive.IsArchivePath(srcPath) {
+ tw := tar.NewWriter(os.Stdout)
+ hdr, err := tar.FileInfoHeader(srcfi, "")
+ if err != nil {
+ return err
+ }
+ err = tw.WriteHeader(hdr)
+ if err != nil {
+ return err
+ }
+ _, err = io.Copy(tw, file)
+ if err != nil {
+ return errors.Wrapf(err, "error streaming archive %s to Stdout", srcPath)
+ }
+ return nil
+ }
+
+ _, err = io.Copy(os.Stdout, file)
+ if err != nil {
+ return errors.Wrapf(err, "error streaming file to Stdout")
+ }
+ return nil
+}
+
+func isVolumeDestName(path string, ctr *libpod.Container) (bool, string, string) {
+ separator := string(os.PathSeparator)
+ if filepath.IsAbs(path) {
+ path = strings.TrimPrefix(path, separator)
+ }
+ if path == "" {
+ return false, "", ""
+ }
+ for _, vol := range ctr.Config().NamedVolumes {
+ volNamePath := strings.TrimPrefix(vol.Dest, separator)
+ if matchVolumePath(path, volNamePath) {
+ return true, vol.Dest, vol.Name
+ }
+ }
+ return false, "", ""
+}
+
+// if SRCPATH or DESTPATH is from volume mount's destination -v or --mount type=volume, generates the path with volume mount point
+func pathWithVolumeMount(ctr *libpod.Container, runtime *libpod.Runtime, volDestName, volName, path string) (string, error) {
+ destVolume, err := runtime.GetVolume(volName)
+ if err != nil {
+ return "", errors.Wrapf(err, "error getting volume destination %s", volName)
+ }
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(string(os.PathSeparator), path)
+ }
+ path, err = securejoin.SecureJoin(destVolume.MountPoint(), strings.TrimPrefix(path, volDestName))
+ return path, err
+}
+
+func isBindMountDestName(path string, ctr *libpod.Container) (bool, specs.Mount) {
+ separator := string(os.PathSeparator)
+ if filepath.IsAbs(path) {
+ path = strings.TrimPrefix(path, string(os.PathSeparator))
+ }
+ if path == "" {
+ return false, specs.Mount{}
+ }
+ for _, m := range ctr.Config().Spec.Mounts {
+ if m.Type != "bind" {
+ continue
+ }
+ mDest := strings.TrimPrefix(m.Destination, separator)
+ if matchVolumePath(path, mDest) {
+ return true, m
+ }
+ }
+ return false, specs.Mount{}
+}
+
+func matchVolumePath(path, target string) bool {
+ pathStr := filepath.Clean(path)
+ target = filepath.Clean(target)
+ for len(pathStr) > len(target) && strings.Contains(pathStr, string(os.PathSeparator)) {
+ pathStr = pathStr[:strings.LastIndex(pathStr, string(os.PathSeparator))]
+ }
+ return pathStr == target
+}
+
+func pathWithBindMountSource(m specs.Mount, path string) (string, error) {
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(string(os.PathSeparator), path)
+ }
+ return securejoin.SecureJoin(m.Source, strings.TrimPrefix(path, m.Destination))
+}
diff --git a/pkg/domain/infra/abi/events.go b/pkg/domain/infra/abi/events.go
index 9540a5b96..20773cdce 100644
--- a/pkg/domain/infra/abi/events.go
+++ b/pkg/domain/infra/abi/events.go
@@ -1,5 +1,3 @@
-//+build ABISupport
-
package abi
import (
diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go
new file mode 100644
index 000000000..f69ba560e
--- /dev/null
+++ b/pkg/domain/infra/abi/generate.go
@@ -0,0 +1,174 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/systemd/generate"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
+ opts := generate.Options{
+ Files: options.Files,
+ New: options.New,
+ }
+
+ // First assume it's a container.
+ if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil {
+ return nil, err
+ } else if found && err == nil {
+ output, err := generate.CreateContainerSystemdUnit(info, opts)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.GenerateSystemdReport{Output: output}, nil
+ }
+
+ // --new does not support pods.
+ if options.New {
+ return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
+ }
+
+ // We're either having a pod or garbage.
+ pod, err := ic.Libpod.LookupPod(nameOrID)
+ if err != nil {
+ return nil, err
+ }
+
+ // Error out if the pod has no infra container, which we require to be the
+ // main service.
+ if !pod.HasInfraContainer() {
+ return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
+ }
+
+ // Generate a systemdgen.ContainerInfo for the infra container. This
+ // ContainerInfo acts as the main service of the pod.
+ infraID, err := pod.InfraContainerID()
+ if err != nil {
+ return nil, nil
+ }
+ podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options)
+ if err != nil {
+ return nil, err
+ }
+
+ // Compute the container-dependency graph for the Pod.
+ containers, err := pod.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ if len(containers) == 0 {
+ return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
+ }
+ graph, err := libpod.BuildContainerGraph(containers)
+ if err != nil {
+ return nil, err
+ }
+
+ // Traverse the dependency graph and create systemdgen.ContainerInfo's for
+ // each container.
+ containerInfos := []*generate.ContainerInfo{podInfo}
+ for ctr, dependencies := range graph.DependencyMap() {
+ // Skip the infra container as we already generated it.
+ if ctr.ID() == infraID {
+ continue
+ }
+ ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options)
+ if err != nil {
+ return nil, err
+ }
+ // Now add the container's dependencies and at the container as a
+ // required service of the infra container.
+ for _, dep := range dependencies {
+ if dep.ID() == infraID {
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
+ } else {
+ _, serviceName := generateServiceName(dep, nil, options)
+ ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
+ }
+ }
+ podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
+ containerInfos = append(containerInfos, ctrInfo)
+ }
+
+ // Now generate the systemd service for all containers.
+ builder := strings.Builder{}
+ for i, info := range containerInfos {
+ if i > 0 {
+ builder.WriteByte('\n')
+ }
+ out, err := generate.CreateContainerSystemdUnit(info, opts)
+ if err != nil {
+ return nil, err
+ }
+ builder.WriteString(out)
+ }
+
+ return &entities.GenerateSystemdReport{Output: builder.String()}, nil
+}
+
+// generateSystemdgenContainerInfo is a helper to generate a
+// systemdgen.ContainerInfo for `GenerateSystemd`.
+func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) {
+ ctr, err := ic.Libpod.LookupContainer(nameOrID)
+ if err != nil {
+ return nil, false, err
+ }
+
+ timeout := ctr.StopTimeout()
+ if options.StopTimeout != nil {
+ timeout = *options.StopTimeout
+ }
+
+ config := ctr.Config()
+ conmonPidFile := config.ConmonPidFile
+ if conmonPidFile == "" {
+ return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
+ }
+
+ createCommand := []string{}
+ if config.CreateCommand != nil {
+ createCommand = config.CreateCommand
+ } else if options.New {
+ return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID)
+ }
+
+ name, serviceName := generateServiceName(ctr, pod, options)
+ info := &generate.ContainerInfo{
+ ServiceName: serviceName,
+ ContainerName: name,
+ RestartPolicy: options.RestartPolicy,
+ PIDFile: conmonPidFile,
+ StopTimeout: timeout,
+ GenerateTimestamp: true,
+ CreateCommand: createCommand,
+ }
+
+ return info, true, nil
+}
+
+// generateServiceName generates the container name and the service name for systemd service.
+func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) {
+ var kind, name, ctrName string
+ if pod == nil {
+ kind = "container"
+ name = ctr.ID()
+ if options.Name {
+ name = ctr.Name()
+ }
+ ctrName = name
+ } else {
+ kind = "pod"
+ name = pod.ID()
+ ctrName = ctr.ID()
+ if options.Name {
+ name = pod.Name()
+ ctrName = ctr.Name()
+ }
+ }
+ return ctrName, fmt.Sprintf("%s-%s", kind, name)
+}
diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go
index 699483243..351bf4f7e 100644
--- a/pkg/domain/infra/abi/healthcheck.go
+++ b/pkg/domain/infra/abi/healthcheck.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go
index 9467c14d4..be788b2bf 100644
--- a/pkg/domain/infra/abi/images.go
+++ b/pkg/domain/infra/abi/images.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
@@ -23,6 +21,7 @@ import (
domainUtils "github.com/containers/libpod/pkg/domain/utils"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
+ "github.com/hashicorp/go-multierror"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -36,76 +35,6 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
return &entities.BoolReport{Value: err == nil}, nil
}
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
- report := entities.ImageDeleteReport{}
-
- if opts.All {
- var previousTargets []*libpodImage.Image
- repeatRun:
- targets, err := ir.Libpod.ImageRuntime().GetRWImages()
- if err != nil {
- return &report, errors.Wrapf(err, "unable to query local images")
- }
- if len(targets) == 0 {
- return &report, nil
- }
- if len(targets) > 0 && len(targets) == len(previousTargets) {
- return &report, errors.New("unable to delete all images; re-run the rmi command again.")
- }
- previousTargets = targets
-
- for _, img := range targets {
- isParent, err := img.IsParent(ctx)
- if err != nil {
- return &report, err
- }
- if isParent {
- continue
- }
- err = ir.deleteImage(ctx, img, opts, report)
- report.Errors = append(report.Errors, err)
- }
- if len(previousTargets) != 1 {
- goto repeatRun
- }
- return &report, nil
- }
-
- for _, id := range nameOrId {
- image, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
- if err != nil {
- return nil, err
- }
-
- err = ir.deleteImage(ctx, image, opts, report)
- if err != nil {
- return &report, err
- }
- }
- return &report, nil
-}
-
-func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error {
- results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
- switch errors.Cause(err) {
- case nil:
- break
- case storage.ErrImageUsedByContainer:
- report.ImageInUse = errors.New(
- fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
- return nil
- case libpodImage.ErrNoSuchImage:
- report.ImageNotFound = err
- return nil
- default:
- return err
- }
-
- report.Deleted = append(report.Deleted, results.Deleted)
- report.Untagged = append(report.Untagged, results.Untagged...)
- return nil
-}
-
func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) {
results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter)
if err != nil {
@@ -117,7 +46,6 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
Id: results,
Err: nil,
},
- Size: 0,
}
return &report, nil
}
@@ -172,7 +100,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
if imageRef.Transport().Name() == dockerarchive.Transport.Name() {
newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer)
if err != nil {
- return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
+ return nil, err
}
return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil
}
@@ -190,13 +118,13 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
DockerCertPath: options.CertDir,
OSChoice: options.OverrideOS,
ArchitectureChoice: options.OverrideArch,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
if !options.AllTags {
newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways)
if err != nil {
- return nil, errors.Wrapf(err, "error pulling image %q", rawImage)
+ return nil, err
}
return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil
}
@@ -237,34 +165,29 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
}
if len(tags) != len(foundIDs) {
- return nil, errors.Errorf("error pulling image %q", rawImage)
+ return nil, err
}
return &entities.ImagePullReport{Images: foundIDs}, nil
}
-func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) {
- report := entities.ImageInspectReport{
- Errors: make(map[string]error),
- }
-
- for _, id := range names {
- img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
+func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) {
+ reports := []*entities.ImageInspectReport{}
+ for _, i := range namesOrIDs {
+ img, err := ir.Libpod.ImageRuntime().NewFromLocal(i)
if err != nil {
- report.Errors[id] = err
- continue
+ return nil, err
}
-
- results, err := img.Inspect(ctx)
+ result, err := img.Inspect(ctx)
if err != nil {
- report.Errors[id] = err
- continue
+ return nil, err
}
-
- cookedResults := entities.ImageData{}
- _ = domainUtils.DeepCopy(&cookedResults, results)
- report.Images = append(report.Images, &cookedResults)
+ report := entities.ImageInspectReport{}
+ if err := domainUtils.DeepCopy(&report, result); err != nil {
+ return nil, err
+ }
+ reports = append(reports, &report)
}
- return &report, nil
+ return reports, nil
}
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error {
@@ -298,7 +221,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
- DockerInsecureSkipTLSVerify: options.TLSVerify,
+ DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
signOptions := image.SigningOptions{
@@ -374,6 +297,10 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string
if err != nil {
return err
}
+ // If only one arg is provided, all names are to be untagged
+ if len(tags) == 0 {
+ tags = newImage.Names()
+ }
for _, tag := range tags {
if err := newImage.UntagImage(tag); err != nil {
return err
@@ -393,16 +320,19 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions)
if err != nil {
return nil, err
}
- newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name)
- if err != nil {
- return nil, errors.Wrap(err, "image loaded but no additional tags were created")
- }
- if len(opts.Name) > 0 {
- if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil {
- return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName)
+ names := strings.Split(name, ",")
+ if len(names) <= 1 {
+ newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name)
+ if err != nil {
+ return nil, errors.Wrap(err, "image loaded but no additional tags were created")
+ }
+ if len(opts.Name) > 0 {
+ if err := newImage.TagImage(fmt.Sprintf("%s:%s", opts.Name, opts.Tag)); err != nil {
+ return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName)
+ }
}
}
- return &entities.ImageLoadReport{Name: name}, nil
+ return &entities.ImageLoadReport{Names: names}, nil
}
func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) {
@@ -440,7 +370,7 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
Filter: *filter,
Limit: opts.Limit,
NoTrunc: opts.NoTrunc,
- InsecureSkipTLSVerify: opts.TLSVerify,
+ InsecureSkipTLSVerify: opts.SkipTLSVerify,
}
searchResults, err := image.SearchImages(term, searchOpts)
@@ -468,3 +398,167 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
return ir.Libpod.GetConfig()
}
+
+func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) {
+ id, _, err := ir.Libpod.Build(ctx, opts.BuildOptions, containerFiles...)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.BuildReport{ID: id}, nil
+}
+
+func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) {
+ img, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId)
+ if err != nil {
+ return nil, err
+ }
+ results, err := img.GenerateTree(opts.WhatRequires)
+ if err != nil {
+ return nil, err
+ }
+ return &entities.ImageTreeReport{Tree: results}, nil
+}
+
+// Remove removes one or more images from local storage.
+func (ir *ImageEngine) Remove(ctx context.Context, images []string, opts entities.ImageRemoveOptions) (report *entities.ImageRemoveReport, finalError error) {
+ var (
+ // noSuchImageErrors indicates that at least one image was not found.
+ noSuchImageErrors bool
+ // inUseErrors indicates that at least one image is being used by a
+ // container.
+ inUseErrors bool
+ // otherErrors indicates that at least one error other than the two
+ // above occured.
+ otherErrors bool
+ // deleteError is a multierror to conveniently collect errors during
+ // removal. We really want to delete as many images as possible and not
+ // error out immediately.
+ deleteError *multierror.Error
+ )
+
+ report = &entities.ImageRemoveReport{}
+
+ // Set the removalCode and the error after all work is done.
+ defer func() {
+ switch {
+ // 2
+ case inUseErrors:
+ // One of the specified images has child images or is
+ // being used by a container.
+ report.ExitCode = 2
+ // 1
+ case noSuchImageErrors && !(otherErrors || inUseErrors):
+ // One of the specified images did not exist, and no other
+ // failures.
+ report.ExitCode = 1
+ // 0
+ default:
+ // Nothing to do.
+ }
+ if deleteError != nil {
+ // go-multierror has a trailing new line which we need to remove to normalize the string.
+ finalError = deleteError.ErrorOrNil()
+ finalError = errors.New(strings.TrimSpace(finalError.Error()))
+ }
+ }()
+
+ // deleteImage is an anonymous function to conveniently delete an image
+ // without having to pass all local data around.
+ deleteImage := func(img *image.Image) error {
+ results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force)
+ switch errors.Cause(err) {
+ case nil:
+ break
+ case define.ErrNoSuchImage:
+ inUseErrors = true // ExitCode is expected
+ case storage.ErrImageUsedByContainer:
+ inUseErrors = true // Important for exit codes in Podman.
+ return errors.New(
+ fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()))
+ case define.ErrImageInUse:
+ inUseErrors = true
+ return err
+ default:
+ otherErrors = true // Important for exit codes in Podman.
+ return err
+ }
+
+ report.Deleted = append(report.Deleted, results.Deleted)
+ report.Untagged = append(report.Untagged, results.Untagged...)
+ return nil
+ }
+
+ // Delete all images from the local storage.
+ if opts.All {
+ previousImages := 0
+ // Remove all images one-by-one.
+ for {
+ storageImages, err := ir.Libpod.ImageRuntime().GetRWImages()
+ if err != nil {
+ deleteError = multierror.Append(deleteError,
+ errors.Wrapf(err, "unable to query local images"))
+ otherErrors = true // Important for exit codes in Podman.
+ return
+ }
+ // No images (left) to remove, so we're done.
+ if len(storageImages) == 0 {
+ return
+ }
+ // Prevent infinity loops by making a delete-progress check.
+ if previousImages == len(storageImages) {
+ otherErrors = true // Important for exit codes in Podman.
+ deleteError = multierror.Append(deleteError,
+ errors.New("unable to delete all images, check errors and re-run image removal if needed"))
+ break
+ }
+ previousImages = len(storageImages)
+ // Delete all "leaves" (i.e., images without child images).
+ for _, img := range storageImages {
+ isParent, err := img.IsParent(ctx)
+ if err != nil {
+ otherErrors = true // Important for exit codes in Podman.
+ deleteError = multierror.Append(deleteError, err)
+ }
+ // Skip parent images.
+ if isParent {
+ continue
+ }
+ if err := deleteImage(img); err != nil {
+ deleteError = multierror.Append(deleteError, err)
+ }
+ }
+ }
+
+ return
+ }
+
+ // Delete only the specified images.
+ for _, id := range images {
+ img, err := ir.Libpod.ImageRuntime().NewFromLocal(id)
+ switch errors.Cause(err) {
+ case nil:
+ break
+ case image.ErrNoSuchImage:
+ noSuchImageErrors = true // Important for exit codes in Podman.
+ fallthrough
+ default:
+ deleteError = multierror.Append(deleteError, errors.Wrapf(err, "failed to remove image '%s'", id))
+ continue
+ }
+
+ err = deleteImage(img)
+ if err != nil {
+ otherErrors = true // Important for exit codes in Podman.
+ deleteError = multierror.Append(deleteError, err)
+ }
+ }
+
+ return
+}
+
+// Shutdown Libpod engine
+func (ir *ImageEngine) Shutdown(_ context.Context) {
+ shutdownSync.Do(func() {
+ _ = ir.Libpod.Shutdown(false)
+ })
+}
diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go
index 2f4020374..c559e250c 100644
--- a/pkg/domain/infra/abi/images_list.go
+++ b/pkg/domain/infra/abi/images_list.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
@@ -27,8 +25,8 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
return nil, err
}
- summaries := make([]*entities.ImageSummary, len(images))
- for i, img := range images {
+ var summaries []*entities.ImageSummary
+ for _, img := range images {
var repoTags []string
if opts.All {
pairs, err := libpodImage.ReposToMap(img.Names())
@@ -43,6 +41,9 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
}
} else {
repoTags, _ = img.RepoTags()
+ if len(repoTags) == 0 {
+ continue
+ }
}
digests := make([]string, len(img.Digests()))
@@ -54,7 +55,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
ID: img.ID(),
ConfigDigest: string(img.ConfigDigest),
- Created: img.Created().Unix(),
+ Created: img.Created(),
Dangling: img.Dangling(),
Digest: string(img.Digest()),
Digests: digests,
@@ -74,7 +75,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions)
sz, _ := img.Size(context.TODO())
e.Size = int64(*sz)
- summaries[i] = &e
+ summaries = append(summaries, &e)
}
return summaries, nil
}
diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go
new file mode 100644
index 000000000..88331f96c
--- /dev/null
+++ b/pkg/domain/infra/abi/manifest.go
@@ -0,0 +1,102 @@
+// +build ABISupport
+
+package abi
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ buildahUtil "github.com/containers/buildah/util"
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/transports/alltransports"
+ libpodImage "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/util"
+
+ "github.com/pkg/errors"
+)
+
+// ManifestCreate implements logic for creating manifest lists via ImageEngine
+func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
+ fullNames, err := buildahUtil.ExpandNames(names, "", ir.Libpod.SystemContext(), ir.Libpod.GetStore())
+ if err != nil {
+ return "", errors.Wrapf(err, "error encountered while expanding image name %q", names)
+ }
+ imageID, err := libpodImage.CreateManifestList(ir.Libpod.ImageRuntime(), *ir.Libpod.SystemContext(), fullNames, images, opts.All)
+ if err != nil {
+ return imageID, err
+ }
+ return imageID, err
+}
+
+// ManifestInspect returns the content of a manifest list or image
+func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
+ dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+ _, err := alltransports.ParseImageName(name)
+ if err != nil {
+ _, err = alltransports.ParseImageName(dockerPrefix + name)
+ if err != nil {
+ return nil, errors.Errorf("invalid image reference %q", name)
+ }
+ }
+ image, err := ir.Libpod.ImageRuntime().New(ctx, name, "", "", nil, nil, libpodImage.SigningOptions{}, nil, util.PullImageMissing)
+ if err != nil {
+ return nil, errors.Wrapf(err, "reading image %q", name)
+ }
+
+ list, err := image.InspectManifest()
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading manifest %q", name)
+ }
+ buf, err := json.MarshalIndent(list, "", " ")
+ if err != nil {
+ return buf, errors.Wrapf(err, "error rendering manifest for display")
+ }
+ return buf, nil
+}
+
+// ManifestAdd adds images to the manifest list
+func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
+ imageSpec := opts.Images[0]
+ listImageSpec := opts.Images[1]
+ dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name())
+ _, err := alltransports.ParseImageName(imageSpec)
+ if err != nil {
+ _, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, imageSpec))
+ if err != nil {
+ return "", errors.Errorf("invalid image reference %q", imageSpec)
+ }
+ }
+ listImage, err := ir.Libpod.ImageRuntime().NewFromLocal(listImageSpec)
+ if err != nil {
+ return "", errors.Wrapf(err, "error retriving local image from image name %s", listImageSpec)
+ }
+
+ manifestAddOpts := libpodImage.ManifestAddOpts{
+ All: opts.All,
+ Arch: opts.Arch,
+ Features: opts.Features,
+ Images: opts.Images,
+ OS: opts.OS,
+ OSVersion: opts.OSVersion,
+ Variant: opts.Variant,
+ }
+ if len(opts.Annotation) != 0 {
+ annotations := make(map[string]string)
+ for _, annotationSpec := range opts.Annotation {
+ spec := strings.SplitN(annotationSpec, "=", 2)
+ if len(spec) != 2 {
+ return "", errors.Errorf("no value given for annotation %q", spec[0])
+ }
+ annotations[spec[0]] = spec[1]
+ }
+ manifestAddOpts.Annotation = annotations
+ }
+ listID, err := listImage.AddManifest(*ir.Libpod.SystemContext(), manifestAddOpts)
+ if err != nil {
+ return listID, err
+ }
+ return listID, nil
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 59bf0f636..b286bcf0d 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package abi
import (
@@ -147,7 +145,7 @@ func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, opt
reports []*entities.PodStopReport
)
pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
- if err != nil {
+ if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) {
return nil, err
}
for _, p := range pods {
@@ -182,6 +180,7 @@ func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string,
errs, err := p.Restart(ctx)
if err != nil {
report.Errs = []error{err}
+ reports = append(reports, &report)
continue
}
if len(errs) > 0 {
@@ -209,6 +208,7 @@ func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, op
errs, err := p.Start(ctx)
if err != nil {
report.Errs = []error{err}
+ reports = append(reports, &report)
continue
}
if len(errs) > 0 {
@@ -228,7 +228,7 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
reports []*entities.PodRmReport
)
pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
- if err != nil {
+ if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) {
return nil, err
}
for _, p := range pods {
@@ -236,13 +236,29 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
err := ic.Libpod.RemovePod(ctx, p, true, options.Force)
if err != nil {
report.Err = err
- continue
}
reports = append(reports, &report)
}
return reports, nil
}
+func (ic *ContainerEngine) PodPrune(ctx context.Context, options entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
+ var (
+ reports []*entities.PodPruneReport
+ )
+ response, err := ic.Libpod.PrunePods(ctx)
+ if err != nil {
+ return nil, err
+ }
+ for k, v := range response {
+ reports = append(reports, &entities.PodPruneReport{
+ Err: v,
+ Id: k,
+ })
+ }
+ return reports, nil
+}
+
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
@@ -277,9 +293,12 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp
func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) {
var (
+ err error
filters []libpod.PodFilter
+ pds []*libpod.Pod
reports []*entities.ListPodsReport
)
+
for k, v := range options.Filters {
for _, filter := range v {
f, err := lpfilters.GeneratePodFilterFunc(k, filter)
@@ -290,10 +309,19 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti
}
}
- pds, err := ic.Libpod.Pods(filters...)
- if err != nil {
- return nil, err
+ if options.Latest {
+ pod, err := ic.Libpod.GetLatestPod()
+ if err != nil {
+ return nil, err
+ }
+ pds = append(pds, pod)
+ } else {
+ pds, err = ic.Libpod.Pods(filters...)
+ if err != nil {
+ return nil, err
+ }
}
+
for _, p := range pds {
var lpcs []*entities.ListPodContainer
status, err := p.GetPodStatus()
diff --git a/pkg/domain/infra/abi/pods_stats.go b/pkg/domain/infra/abi/pods_stats.go
new file mode 100644
index 000000000..a41c01da0
--- /dev/null
+++ b/pkg/domain/infra/abi/pods_stats.go
@@ -0,0 +1,85 @@
+package abi
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/docker/go-units"
+ "github.com/pkg/errors"
+)
+
+// PodStats implements printing stats about pods.
+func (ic *ContainerEngine) PodStats(ctx context.Context, namesOrIds []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) {
+ // Cgroups v2 check for rootless.
+ if rootless.IsRootless() {
+ unified, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return nil, err
+ }
+ if !unified {
+ return nil, errors.New("pod stats is not supported in rootless mode without cgroups v2")
+ }
+ }
+ // Get the (running) pods and convert them to the entities format.
+ pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
+ if err != nil {
+ return nil, errors.Wrap(err, "unable to get list of pods")
+ }
+ return ic.podsToStatsReport(pods)
+}
+
+// podsToStatsReport converts a slice of pods into a corresponding slice of stats reports.
+func (ic *ContainerEngine) podsToStatsReport(pods []*libpod.Pod) ([]*entities.PodStatsReport, error) {
+ reports := []*entities.PodStatsReport{}
+ for i := range pods { // Access by index to prevent potential loop-variable leaks.
+ podStats, err := pods[i].GetPodStats(nil)
+ if err != nil {
+ return nil, err
+ }
+ podID := pods[i].ID()[:12]
+ for j := range podStats {
+ r := entities.PodStatsReport{
+ CPU: floatToPercentString(podStats[j].CPU),
+ MemUsage: combineHumanValues(podStats[j].MemUsage, podStats[j].MemLimit),
+ Mem: floatToPercentString(podStats[j].MemPerc),
+ NetIO: combineHumanValues(podStats[j].NetInput, podStats[j].NetOutput),
+ BlockIO: combineHumanValues(podStats[j].BlockInput, podStats[j].BlockOutput),
+ PIDS: pidsToString(podStats[j].PIDs),
+ CID: podStats[j].ContainerID[:12],
+ Name: podStats[j].Name,
+ Pod: podID,
+ }
+ reports = append(reports, &r)
+ }
+ }
+
+ return reports, nil
+}
+
+func combineHumanValues(a, b uint64) string {
+ if a == 0 && b == 0 {
+ return "-- / --"
+ }
+ return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
+}
+
+func floatToPercentString(f float64) string {
+ strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
+ if err != nil || strippedFloat == 0 {
+ // If things go bazinga, return a safe value
+ return "--"
+ }
+ return fmt.Sprintf("%.2f", strippedFloat) + "%"
+}
+
+func pidsToString(pid uint64) string {
+ if pid == 0 {
+ // If things go bazinga, return a safe value
+ return "--"
+ }
+ return fmt.Sprintf("%d", pid)
+}
diff --git a/pkg/domain/infra/abi/runtime.go b/pkg/domain/infra/abi/runtime.go
index b53fb6d3a..fba422d8e 100644
--- a/pkg/domain/infra/abi/runtime.go
+++ b/pkg/domain/infra/abi/runtime.go
@@ -1,8 +1,8 @@
-// +build ABISupport
-
package abi
import (
+ "sync"
+
"github.com/containers/libpod/libpod"
)
@@ -15,3 +15,5 @@ type ImageEngine struct {
type ContainerEngine struct {
Libpod *libpod.Runtime
}
+
+var shutdownSync sync.Once
diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go
index 67593b2dd..e5c109ee6 100644
--- a/pkg/domain/infra/abi/system.go
+++ b/pkg/domain/infra/abi/system.go
@@ -1,20 +1,15 @@
-// +build ABISupport
-
package abi
import (
"context"
"fmt"
"io/ioutil"
- "net"
"os"
"strconv"
- "strings"
"syscall"
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod/define"
- api "github.com/containers/libpod/pkg/api/server"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
@@ -33,39 +28,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
return ic.Libpod.Info()
}
-func (ic *ContainerEngine) RestService(_ context.Context, opts entities.ServiceOptions) error {
- var (
- listener net.Listener
- err error
- )
-
- if opts.URI != "" {
- fields := strings.Split(opts.URI, ":")
- if len(fields) == 1 {
- return errors.Errorf("%s is an invalid socket destination", opts.URI)
- }
- address := strings.Join(fields[1:], ":")
- listener, err = net.Listen(fields[0], address)
- if err != nil {
- return errors.Wrapf(err, "unable to create socket %s", opts.URI)
- }
- }
-
- server, err := api.NewServerWithSettings(ic.Libpod, opts.Timeout, &listener)
- if err != nil {
- return err
- }
- defer func() {
- if err := server.Shutdown(); err != nil {
- logrus.Warnf("Error when stopping API service: %s", err)
- }
- }()
-
- err = server.Serve()
- _ = listener.Close()
- return err
-}
-
func (ic *ContainerEngine) VarlinkService(_ context.Context, opts entities.ServiceOptions) error {
var varlinkInterfaces = []*iopodman.VarlinkInterface{
iopodmanAPI.New(opts.Command, ic.Libpod),
diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go
index d7f5853d8..b422e549e 100644
--- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go
+++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package terminal
import (
diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go
index f187bdd6b..0fc3af511 100644
--- a/pkg/domain/infra/abi/terminal/terminal.go
+++ b/pkg/domain/infra/abi/terminal/terminal.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package terminal
import (
diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go
index 664205df1..15701342f 100644
--- a/pkg/domain/infra/abi/terminal/terminal_linux.go
+++ b/pkg/domain/infra/abi/terminal/terminal_linux.go
@@ -1,5 +1,3 @@
-// +build ABISupport
-
package terminal
import (
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index 0dbcf2ad2..7aa6986a7 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -12,7 +12,7 @@ import (
)
// NewContainerEngine factory provides a libpod runtime for container-related operations
-func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
r, err := NewLibpodRuntime(facts.FlagSet, facts)
@@ -25,7 +25,7 @@ func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine,
}
// NewContainerEngine factory provides a libpod runtime for image-related operations
-func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
diff --git a/pkg/domain/infra/runtime_image_proxy.go b/pkg/domain/infra/runtime_image_proxy.go
index 535fba858..ea5d0e6f2 100644
--- a/pkg/domain/infra/runtime_image_proxy.go
+++ b/pkg/domain/infra/runtime_image_proxy.go
@@ -12,7 +12,7 @@ import (
// ContainerEngine Image Proxy will be EOL'ed after podman is separated from libpod repo
-func NewLibpodImageRuntime(flags *pflag.FlagSet, opts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewLibpodImageRuntime(flags *pflag.FlagSet, opts *entities.PodmanConfig) (entities.ImageEngine, error) {
r, err := GetRuntime(context.Background(), flags, opts)
if err != nil {
return nil, err
diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go
index e335dd560..7c9180d43 100644
--- a/pkg/domain/infra/runtime_libpod.go
+++ b/pkg/domain/infra/runtime_libpod.go
@@ -6,6 +6,7 @@ import (
"context"
"fmt"
"os"
+ "sync"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/cgroups"
@@ -18,17 +19,25 @@ import (
flag "github.com/spf13/pflag"
)
+var (
+ // runtimeSync only guards the non-specialized runtime
+ runtimeSync sync.Once
+ // The default GetRuntime() always returns the same object and error
+ runtimeLib *libpod.Runtime
+ runtimeErr error
+)
+
type engineOpts struct {
name string
renumber bool
migrate bool
noStore bool
withFDS bool
- config entities.PodmanConfig
+ config *entities.PodmanConfig
}
// GetRuntimeMigrate gets a libpod runtime that will perform a migration of existing containers
-func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) {
+func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig, newRuntime string) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
name: newRuntime,
renumber: false,
@@ -40,7 +49,7 @@ func GetRuntimeMigrate(ctx context.Context, fs *flag.FlagSet, cfg entities.Podma
}
// GetRuntimeDisableFDs gets a libpod runtime that will disable sd notify
-func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: false,
migrate: false,
@@ -51,7 +60,7 @@ func GetRuntimeDisableFDs(ctx context.Context, fs *flag.FlagSet, cfg entities.Po
}
// GetRuntimeRenumber gets a libpod runtime that will perform a lock renumber
-func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: true,
migrate: false,
@@ -62,18 +71,21 @@ func GetRuntimeRenumber(ctx context.Context, fs *flag.FlagSet, cfg entities.Podm
}
// GetRuntime generates a new libpod runtime configured by command line options
-func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
- return getRuntime(ctx, flags, &engineOpts{
- renumber: false,
- migrate: false,
- noStore: false,
- withFDS: true,
- config: cfg,
+func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
+ runtimeSync.Do(func() {
+ runtimeLib, runtimeErr = getRuntime(ctx, flags, &engineOpts{
+ renumber: false,
+ migrate: false,
+ noStore: false,
+ withFDS: true,
+ config: cfg,
+ })
})
+ return runtimeLib, runtimeErr
}
// GetRuntimeNoStore generates a new libpod runtime configured by command line options
-func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg entities.PodmanConfig) (*libpod.Runtime, error) {
+func GetRuntimeNoStore(ctx context.Context, fs *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
return getRuntime(ctx, fs, &engineOpts{
renumber: false,
migrate: false,
@@ -160,7 +172,7 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
}
if fs.Changed("runtime") {
- options = append(options, libpod.WithOCIRuntime(cfg.Engine.OCIRuntime))
+ options = append(options, libpod.WithOCIRuntime(cfg.RuntimePath))
}
if fs.Changed("conmon") {
@@ -234,6 +246,18 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
HostGIDMapping: true,
}
+ if mode.IsAuto() {
+ var err error
+ options.HostUIDMapping = false
+ options.HostGIDMapping = false
+ options.AutoUserNs = true
+ opts, err := mode.GetAutoOptions()
+ if err != nil {
+ return nil, err
+ }
+ options.AutoUserNsOpts = *opts
+ return &options, nil
+ }
if mode.IsKeepID() {
if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 {
return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
diff --git a/pkg/domain/infra/runtime_proxy.go b/pkg/domain/infra/runtime_proxy.go
index fbe45ea8f..41193fd89 100644
--- a/pkg/domain/infra/runtime_proxy.go
+++ b/pkg/domain/infra/runtime_proxy.go
@@ -12,7 +12,7 @@ import (
// ContainerEngine Proxy will be EOL'ed after podman is separated from libpod repo
-func NewLibpodRuntime(flags *flag.FlagSet, opts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewLibpodRuntime(flags *flag.FlagSet, opts *entities.PodmanConfig) (entities.ContainerEngine, error) {
r, err := GetRuntime(context.Background(), flags, opts)
if err != nil {
return nil, err
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
index 129fdeb2c..752218aaf 100644
--- a/pkg/domain/infra/runtime_tunnel.go
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -11,7 +11,7 @@ import (
"github.com/containers/libpod/pkg/domain/infra/tunnel"
)
-func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine, error) {
+func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct runtime not supported")
@@ -23,7 +23,7 @@ func NewContainerEngine(facts entities.PodmanConfig) (entities.ContainerEngine,
}
// NewImageEngine factory provides a libpod runtime for image-related operations
-func NewImageEngine(facts entities.PodmanConfig) (entities.ImageEngine, error) {
+func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) {
switch facts.EngineMode {
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 679bb371b..32f9c4e36 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -115,11 +115,15 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st
t := int(*options.Timeout)
timeout = &t
}
+
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
+ if options.Running && c.State != define.ContainerStateRunning.String() {
+ continue
+ }
reports = append(reports, &entities.RestartReport{
Id: c.ID,
Err: containers.Restart(ic.ClientCxt, c.ID, timeout),
@@ -371,3 +375,15 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIds []str
func (ic *ContainerEngine) Config(_ context.Context) (*config.Config, error) {
return config.Default()
}
+
+func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrId string, options entities.ContainerPortOptions) ([]*entities.ContainerPortReport, error) {
+ return nil, errors.New("not implemented")
+}
+
+func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) {
+ return nil, errors.New("not implemented")
+}
+
+// Shutdown Libpod engine
+func (ic *ContainerEngine) Shutdown(_ context.Context) {
+}
diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go
index 46d88341a..93da3aeb4 100644
--- a/pkg/domain/infra/tunnel/events.go
+++ b/pkg/domain/infra/tunnel/events.go
@@ -4,7 +4,6 @@ import (
"context"
"strings"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/bindings/system"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@@ -21,10 +20,10 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio
filters[split[0]] = append(filters[split[0]], strings.Join(split[1:], "="))
}
}
- binChan := make(chan handlers.Event)
+ binChan := make(chan entities.Event)
go func() {
for e := range binChan {
- opts.EventChan <- e.ToLibpodEvent()
+ opts.EventChan <- entities.ConvertToLibpodEvent(e)
}
}()
return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters)
diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go
new file mode 100644
index 000000000..3cd483053
--- /dev/null
+++ b/pkg/domain/infra/tunnel/generate.go
@@ -0,0 +1,12 @@
+package tunnel
+
+import (
+ "context"
+
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
+ return nil, errors.New("not implemented for tunnel")
+}
diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go
index 7d40e0327..dcc5fc3e7 100644
--- a/pkg/domain/infra/tunnel/images.go
+++ b/pkg/domain/infra/tunnel/images.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/image/v5/docker/reference"
+ "github.com/containers/libpod/pkg/bindings"
images "github.com/containers/libpod/pkg/bindings/images"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/utils"
@@ -19,25 +20,8 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.Boo
return &entities.BoolReport{Value: found}, err
}
-func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) {
- report := entities.ImageDeleteReport{}
-
- for _, id := range nameOrId {
- results, err := images.Remove(ir.ClientCxt, id, &opts.Force)
- if err != nil {
- return nil, err
- }
- for _, e := range results {
- if a, ok := e["Deleted"]; ok {
- report.Deleted = append(report.Deleted, a)
- }
-
- if a, ok := e["Untagged"]; ok {
- report.Untagged = append(report.Untagged, a)
- }
- }
- }
- return &report, nil
+func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, error) {
+ return images.Remove(ir.ClientCxt, imagesArg, opts)
}
func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) {
@@ -126,6 +110,15 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string,
}
func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error {
+ // Remove all tags if none are provided
+ if len(tags) == 0 {
+ newImage, err := images.GetImage(ir.ClientCxt, nameOrId, &bindings.PFalse)
+ if err != nil {
+ return err
+ }
+ tags = newImage.NamesHistory
+ }
+
for _, newTag := range tags {
var (
tag, repo string
@@ -150,16 +143,16 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string
return nil
}
-func (ir *ImageEngine) Inspect(_ context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) {
- report := entities.ImageInspectReport{}
- for _, id := range names {
- r, err := images.GetImage(ir.ClientCxt, id, &opts.Size)
+func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) {
+ reports := []*entities.ImageInspectReport{}
+ for _, i := range namesOrIDs {
+ r, err := images.GetImage(ir.ClientCxt, i, &opts.Size)
if err != nil {
- report.Errors[id] = err
+ return nil, err
}
- report.Images = append(report.Images, r)
+ reports = append(reports, r)
}
- return &report, nil
+ return reports, nil
}
func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) {
@@ -259,3 +252,15 @@ func (ir *ImageEngine) Search(ctx context.Context, term string, opts entities.Im
func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) {
return config.Default()
}
+
+func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) {
+ return nil, errors.New("not implemented yet")
+}
+
+func (ir *ImageEngine) Tree(ctx context.Context, nameOrId string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) {
+ return nil, errors.New("not implemented yet")
+}
+
+// Shutdown Libpod engine
+func (ir *ImageEngine) Shutdown(_ context.Context) {
+}
diff --git a/pkg/domain/infra/tunnel/manifest.go b/pkg/domain/infra/tunnel/manifest.go
new file mode 100644
index 000000000..18b400533
--- /dev/null
+++ b/pkg/domain/infra/tunnel/manifest.go
@@ -0,0 +1,64 @@
+package tunnel
+
+import (
+ "context"
+ "encoding/json"
+ "strings"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings/manifests"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+)
+
+// ManifestCreate implements manifest create via ImageEngine
+func (ir *ImageEngine) ManifestCreate(ctx context.Context, names, images []string, opts entities.ManifestCreateOptions) (string, error) {
+ imageID, err := manifests.Create(ir.ClientCxt, names, images, &opts.All)
+ if err != nil {
+ return imageID, errors.Wrapf(err, "error creating manifest")
+ }
+ return imageID, err
+}
+
+// ManifestInspect returns contents of manifest list with given name
+func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte, error) {
+ list, err := manifests.Inspect(ir.ClientCxt, name)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error getting content of manifest list or image %s", name)
+ }
+
+ buf, err := json.MarshalIndent(list, "", " ")
+ if err != nil {
+ return buf, errors.Wrapf(err, "error rendering manifest for display")
+ }
+ return buf, err
+}
+
+// ManifestAdd adds images to the manifest list
+func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAddOptions) (string, error) {
+ manifestAddOpts := image.ManifestAddOpts{
+ All: opts.All,
+ Arch: opts.Arch,
+ Features: opts.Features,
+ Images: opts.Images,
+ OS: opts.OS,
+ OSVersion: opts.OSVersion,
+ Variant: opts.Variant,
+ }
+ if len(opts.Annotation) != 0 {
+ annotations := make(map[string]string)
+ for _, annotationSpec := range opts.Annotation {
+ spec := strings.SplitN(annotationSpec, "=", 2)
+ if len(spec) != 2 {
+ return "", errors.Errorf("no value given for annotation %q", spec[0])
+ }
+ annotations[spec[0]] = spec[1]
+ }
+ manifestAddOpts.Annotation = annotations
+ }
+ listID, err := manifests.Add(ctx, opts.Images[1], manifestAddOpts)
+ if err != nil {
+ return listID, errors.Wrapf(err, "error adding to manifest list %s", opts.Images[1])
+ }
+ return listID, nil
+}
diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go
index dad77284f..c193c6752 100644
--- a/pkg/domain/infra/tunnel/pods.go
+++ b/pkg/domain/infra/tunnel/pods.go
@@ -173,6 +173,10 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio
return reports, nil
}
+func (ic *ContainerEngine) PodPrune(ctx context.Context, opts entities.PodPruneOptions) ([]*entities.PodPruneReport, error) {
+ return pods.Prune(ic.ClientCxt)
+}
+
func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) {
podSpec := specgen.NewPodSpecGenerator()
opts.ToPodSpecGen(podSpec)
@@ -207,3 +211,7 @@ func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodI
}
return pods.Inspect(ic.ClientCxt, options.NameOrID)
}
+
+func (ic *ContainerEngine) PodStats(ctx context.Context, namesOrIds []string, options entities.PodStatsOptions) ([]*entities.PodStatsReport, error) {
+ return pods.Stats(ic.ClientCxt, namesOrIds, options)
+}
diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go
index f373525c5..97bf885e7 100644
--- a/pkg/domain/infra/tunnel/system.go
+++ b/pkg/domain/infra/tunnel/system.go
@@ -14,10 +14,6 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) {
return system.Info(ic.ClientCxt)
}
-func (ic *ContainerEngine) RestService(_ context.Context, _ entities.ServiceOptions) error {
- panic(errors.New("rest service is not supported when tunneling"))
-}
-
func (ic *ContainerEngine) VarlinkService(_ context.Context, _ entities.ServiceOptions) error {
panic(errors.New("varlink service is not supported when tunneling"))
}
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index 2cb3c3f20..2ffbde977 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -31,7 +31,7 @@ func (n CgroupMode) IsHost() bool {
// IsDefaultValue indicates whether the cgroup namespace has the default value.
func (n CgroupMode) IsDefaultValue() bool {
- return n == ""
+ return n == "" || n == defaultType
}
// IsNS indicates a cgroup namespace passed in by path (ns:<path>)
@@ -102,6 +102,11 @@ func (n UsernsMode) IsAuto() bool {
return parts[0] == "auto"
}
+// IsDefaultValue indicates whether the user namespace has the default value.
+func (n UsernsMode) IsDefaultValue() bool {
+ return n == "" || n == defaultType
+}
+
// GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically
// a user namespace.
func (n UsernsMode) GetAutoOptions() (*storage.AutoUserNsOptions, error) {
diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go
index d0fef65c8..907063df9 100644
--- a/pkg/ps/ps.go
+++ b/pkg/ps/ps.go
@@ -158,6 +158,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities
ExitedAt: exitedTime.Unix(),
ID: conConfig.ID,
Image: conConfig.RootfsImageName,
+ ImageID: conConfig.RootfsImageID,
IsInfra: conConfig.IsInfra,
Labels: conConfig.Labels,
Mounts: ctr.UserVolumes(),
diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c
index da52a7217..716db81dc 100644
--- a/pkg/rootless/rootless_linux.c
+++ b/pkg/rootless/rootless_linux.c
@@ -535,13 +535,41 @@ create_pause_process (const char *pause_pid_file_path, char **argv)
}
}
+static int
+open_namespace (int pid_to_join, const char *ns_file)
+{
+ char ns_path[PATH_MAX];
+ int ret;
+
+ ret = snprintf (ns_path, PATH_MAX, "/proc/%d/ns/%s", pid_to_join, ns_file);
+ if (ret == PATH_MAX)
+ {
+ fprintf (stderr, "internal error: namespace path too long\n");
+ return -1;
+ }
+
+ return open (ns_path, O_CLOEXEC | O_RDONLY);
+}
+
+static void
+join_namespace_or_die (const char *name, int ns_fd)
+{
+ if (setns (ns_fd, 0) < 0)
+ {
+ fprintf (stderr, "cannot set %s namespace\n", name);
+ _exit (EXIT_FAILURE);
+ }
+}
+
int
-reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
+reexec_userns_join (int pid_to_join, char *pause_pid_file_path)
{
char uid[16];
char gid[16];
char **argv;
int pid;
+ int mnt_ns = -1;
+ int user_ns = -1;
char *cwd = getcwd (NULL, 0);
sigset_t sigset, oldsigset;
@@ -561,14 +589,28 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
+ user_ns = open_namespace (pid_to_join, "user");
+ if (user_ns < 0)
+ return user_ns;
+ mnt_ns = open_namespace (pid_to_join, "mnt");
+ if (mnt_ns < 0)
+ {
+ close (user_ns);
+ return mnt_ns;
+ }
+
pid = fork ();
if (pid < 0)
fprintf (stderr, "cannot fork: %s\n", strerror (errno));
if (pid)
{
- /* We passed down these fds, close them. */
int f;
+
+ /* We passed down these fds, close them. */
+ close (user_ns);
+ close (mnt_ns);
+
for (f = 3; f < open_files_max_fd; f++)
if (open_files_set == NULL || FD_ISSET (f % FD_SETSIZE, &(open_files_set[f / FD_SETSIZE])))
close (f);
@@ -606,19 +648,10 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path)
_exit (EXIT_FAILURE);
}
- if (setns (userns, 0) < 0)
- {
- fprintf (stderr, "cannot setns: %s\n", strerror (errno));
- _exit (EXIT_FAILURE);
- }
- close (userns);
-
- if (mountns >= 0 && setns (mountns, 0) < 0)
- {
- fprintf (stderr, "cannot setns: %s\n", strerror (errno));
- _exit (EXIT_FAILURE);
- }
- close (mountns);
+ join_namespace_or_die ("user", user_ns);
+ join_namespace_or_die ("mnt", mnt_ns);
+ close (user_ns);
+ close (mnt_ns);
if (syscall_setresgid (0, 0, 0) < 0)
{
diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go
index 5ddfab7ad..3de136f12 100644
--- a/pkg/rootless/rootless_linux.go
+++ b/pkg/rootless/rootless_linux.go
@@ -31,7 +31,7 @@ extern uid_t rootless_uid();
extern uid_t rootless_gid();
extern int reexec_in_user_namespace(int ready, char *pause_pid_file_path, char *file_to_read, int fd);
extern int reexec_in_user_namespace_wait(int pid, int options);
-extern int reexec_userns_join(int userns, int mountns, char *pause_pid_file_path);
+extern int reexec_userns_join(int pid, char *pause_pid_file_path);
*/
import "C"
@@ -124,91 +124,6 @@ func tryMappingTool(tool string, pid int, hostID int, mappings []idtools.IDMap)
return nil
}
-func readUserNs(path string) (string, error) {
- b := make([]byte, 256)
- _, err := unix.Readlink(path, b)
- if err != nil {
- return "", err
- }
- return string(b), nil
-}
-
-func readUserNsFd(fd uintptr) (string, error) {
- return readUserNs(fmt.Sprintf("/proc/self/fd/%d", fd))
-}
-
-func getParentUserNs(fd uintptr) (uintptr, error) {
- const nsGetParent = 0xb702
- ret, _, errno := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(nsGetParent), 0)
- if errno != 0 {
- return 0, errno
- }
- return (uintptr)(unsafe.Pointer(ret)), nil
-}
-
-// getUserNSFirstChild returns an open FD for the first direct child user namespace that created the process
-// Each container creates a new user namespace where the runtime runs. The current process in the container
-// might have created new user namespaces that are child of the initial namespace we created.
-// This function finds the initial namespace created for the container that is a child of the current namespace.
-//
-// current ns
-// / \
-// TARGET -> a [other containers]
-// /
-// b
-// /
-// NS READ USING THE PID -> c
-func getUserNSFirstChild(fd uintptr) (*os.File, error) {
- currentNS, err := readUserNs("/proc/self/ns/user")
- if err != nil {
- return nil, err
- }
-
- ns, err := readUserNsFd(fd)
- if err != nil {
- return nil, errors.Wrapf(err, "cannot read user namespace")
- }
- if ns == currentNS {
- return nil, errors.New("process running in the same user namespace")
- }
-
- for {
- nextFd, err := getParentUserNs(fd)
- if err != nil {
- if err == unix.ENOTTY {
- return os.NewFile(fd, "userns child"), nil
- }
- return nil, errors.Wrapf(err, "cannot get parent user namespace")
- }
-
- ns, err = readUserNsFd(nextFd)
- if err != nil {
- return nil, errors.Wrapf(err, "cannot read user namespace")
- }
-
- if ns == currentNS {
- if err := unix.Close(int(nextFd)); err != nil {
- return nil, err
- }
-
- // Drop O_CLOEXEC for the fd.
- _, _, errno := unix.Syscall(unix.SYS_FCNTL, fd, unix.F_SETFD, 0)
- if errno != 0 {
- if err := unix.Close(int(fd)); err != nil {
- logrus.Errorf("failed to close file descriptor %d", fd)
- }
- return nil, errno
- }
-
- return os.NewFile(fd, "userns child"), nil
- }
- if err := unix.Close(int(fd)); err != nil {
- return nil, err
- }
- fd = nextFd
- }
-}
-
// joinUserAndMountNS re-exec podman in a new userNS and join the user and mount
// namespace of the specified PID without looking up its parent. Useful to join directly
// the conmon process.
@@ -220,31 +135,7 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) {
cPausePid := C.CString(pausePid)
defer C.free(unsafe.Pointer(cPausePid))
- userNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/user", pid))
- if err != nil {
- return false, -1, err
- }
- defer func() {
- if err := userNS.Close(); err != nil {
- logrus.Errorf("unable to close namespace: %q", err)
- }
- }()
-
- mountNS, err := os.Open(fmt.Sprintf("/proc/%d/ns/mnt", pid))
- if err != nil {
- return false, -1, err
- }
- defer func() {
- if err := mountNS.Close(); err != nil {
- logrus.Errorf("unable to close namespace: %q", err)
- }
- }()
-
- fd, err := getUserNSFirstChild(userNS.Fd())
- if err != nil {
- return false, -1, err
- }
- pidC := C.reexec_userns_join(C.int(fd.Fd()), C.int(mountNS.Fd()), cPausePid)
+ pidC := C.reexec_userns_join(C.int(pid), cPausePid)
if int(pidC) < 0 {
return false, -1, errors.Errorf("cannot re-exec process")
}
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 1c1ed39df..c686d80fc 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -102,25 +102,27 @@ func parent() error {
return err
}
- sigC := make(chan os.Signal, 1)
- signal.Notify(sigC, unix.SIGPIPE)
- defer func() {
- // dummy signal to terminate the goroutine
- sigC <- unix.SIGKILL
- }()
+ exitC := make(chan os.Signal, 1)
+ defer close(exitC)
+
go func() {
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, unix.SIGPIPE)
defer func() {
signal.Stop(sigC)
close(sigC)
}()
- s := <-sigC
- if s == unix.SIGPIPE {
- if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
- unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
- unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
- f.Close()
+ select {
+ case s := <-sigC:
+ if s == unix.SIGPIPE {
+ if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil {
+ unix.Dup2(int(f.Fd()), 1) // nolint:errcheck
+ unix.Dup2(int(f.Fd()), 2) // nolint:errcheck
+ f.Close()
+ }
}
+ case <-exitC:
}
}()
diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go
new file mode 100644
index 000000000..6b6d065f7
--- /dev/null
+++ b/pkg/selinux/selinux.go
@@ -0,0 +1,40 @@
+package selinux
+
+import (
+ "github.com/opencontainers/selinux/go-selinux"
+)
+
+// KVMLabel returns labels for running kvm isolated containers
+func KVMLabel(cLabel string) (string, error) {
+ if cLabel == "" {
+ // selinux is disabled
+ return "", nil
+ }
+ processLabel, _ := selinux.KVMContainerLabels()
+ selinux.ReleaseLabel(processLabel)
+ return swapSELinuxLabel(cLabel, processLabel)
+}
+
+// InitLabel returns labels for running systemd based containers
+func InitLabel(cLabel string) (string, error) {
+ if cLabel == "" {
+ // selinux is disabled
+ return "", nil
+ }
+ processLabel, _ := selinux.InitContainerLabels()
+ selinux.ReleaseLabel(processLabel)
+ return swapSELinuxLabel(cLabel, processLabel)
+}
+
+func swapSELinuxLabel(cLabel, processLabel string) (string, error) {
+ dcon, err := selinux.NewContext(cLabel)
+ if err != nil {
+ return "", err
+ }
+ scon, err := selinux.NewContext(processLabel)
+ if err != nil {
+ return "", err
+ }
+ dcon["type"] = scon["type"]
+ return dcon.Get(), nil
+}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 5de07fc28..a62344640 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -5,11 +5,11 @@ import (
"github.com/containers/common/pkg/capabilities"
cconfig "github.com/containers/common/pkg/config"
+ "github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/sysinfo"
"github.com/containers/libpod/pkg/util"
"github.com/docker/go-units"
"github.com/opencontainers/runc/libcontainer/user"
@@ -326,10 +326,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
}
defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv)
}
- config.Env = env.Join(defaultEnv, config.Env)
- for name, val := range config.Env {
- g.AddProcessEnv(name, val)
- }
if err := addRlimits(config, &g); err != nil {
return nil, err
@@ -360,6 +356,11 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
if err := config.Cgroup.ConfigureGenerator(&g); err != nil {
return nil, err
}
+
+ config.Env = env.Join(defaultEnv, config.Env)
+ for name, val := range config.Env {
+ g.AddProcessEnv(name, val)
+ }
configSpec := g.Config
// If the container image specifies an label with a
diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go
index 0f63b2bbc..71434fe73 100644
--- a/pkg/spec/spec_test.go
+++ b/pkg/spec/spec_test.go
@@ -4,9 +4,9 @@ import (
"runtime"
"testing"
+ "github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/rootless"
- "github.com/containers/libpod/pkg/sysinfo"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/docker/go-units"
diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go
index 9152e7ee7..94e456c52 100644
--- a/pkg/specgen/container_validate.go
+++ b/pkg/specgen/container_validate.go
@@ -14,7 +14,7 @@ var (
// SystemDValues describes the only values that SystemD can be
SystemDValues = []string{"true", "false", "always"}
// ImageVolumeModeValues describes the only values that ImageVolumeMode can be
- ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"}
+ ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"}
)
func exclusiveOptions(opt1, opt2 string) error {
@@ -34,7 +34,7 @@ func (s *SpecGenerator) Validate() error {
}
// Cannot set hostname and utsns
if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() {
- return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace")
+ return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when running in the host UTS namespace")
}
// systemd values must be true, false, or always
if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) {
@@ -54,7 +54,7 @@ func (s *SpecGenerator) Validate() error {
}
// shmsize conflicts with IPC namespace
if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() {
- return errors.New("cannot set shmsize when creating an IPC namespace")
+ return errors.New("cannot set shmsize when running in the host IPC Namespace")
}
//
@@ -86,18 +86,15 @@ func (s *SpecGenerator) Validate() error {
//
// ContainerNetworkConfig
//
- if !s.NetNS.IsPrivate() && s.ConfigureNetNS {
- return errors.New("can only configure network namespace when creating a network a network namespace")
- }
// useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption
if s.UseImageResolvConf {
- if len(s.DNSServer) > 0 {
+ if len(s.DNSServers) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSServer")
}
if len(s.DNSSearch) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSSearch")
}
- if len(s.DNSOption) > 0 {
+ if len(s.DNSOptions) > 0 {
return exclusiveOptions("UseImageResolvConf", "DNSOption")
}
}
@@ -132,7 +129,7 @@ func (s *SpecGenerator) Validate() error {
if err := s.CgroupNS.validate(); err != nil {
return err
}
- if err := s.UserNS.validate(); err != nil {
+ if err := validateUserNS(&s.UserNS); err != nil {
return err
}
diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go
index b06ef5c9a..5d629a6e6 100644
--- a/pkg/specgen/generate/config_linux_cgo.go
+++ b/pkg/specgen/generate/config_linux_cgo.go
@@ -24,6 +24,9 @@ func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *imag
}
if scp == seccomp.PolicyImage {
+ if img == nil {
+ return nil, errors.New("cannot read seccomp profile without a valid image")
+ }
labels, err := img.Labels(context.Background())
if err != nil {
return nil, err
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
index 7233acb8a..92a2b4d35 100644
--- a/pkg/specgen/generate/container.go
+++ b/pkg/specgen/generate/container.go
@@ -3,34 +3,60 @@ package generate
import (
"context"
+ "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod"
ann "github.com/containers/libpod/pkg/annotations"
envLib "github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/specgen"
- "github.com/pkg/errors"
"golang.org/x/sys/unix"
)
func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error {
+ // If a rootfs is used, then there is no image data
+ if s.ContainerStorageConfig.Rootfs != "" {
+ return nil
+ }
newImage, err := r.ImageRuntime().NewFromLocal(s.Image)
if err != nil {
return err
}
+ _, mediaType, err := newImage.Manifest(ctx)
+ if err != nil {
+ return err
+ }
+
+ if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType {
+ s.HealthConfig, err = newImage.GetHealthCheck(ctx)
+ if err != nil {
+ return err
+ }
+ }
+
// Image stop signal
- if s.StopSignal == nil && newImage.Config != nil {
- sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal)
+ if s.StopSignal == nil {
+ stopSignal, err := newImage.StopSignal(ctx)
+ if err != nil {
+ return err
+ }
+ sig, err := signal.ParseSignalNameOrNumber(stopSignal)
if err != nil {
return err
}
s.StopSignal = &sig
}
+
// Image envs from the image if they don't exist
// already
- if newImage.Config != nil && len(newImage.Config.Env) > 0 {
- envs, err := envLib.ParseSlice(newImage.Config.Env)
+ env, err := newImage.Env(ctx)
+ if err != nil {
+ return err
+ }
+
+ if len(env) > 0 {
+ envs, err := envLib.ParseSlice(env)
if err != nil {
return err
}
@@ -41,16 +67,29 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
}
}
+ labels, err := newImage.Labels(ctx)
+ if err != nil {
+ return err
+ }
+
// labels from the image that dont exist already
- if config := newImage.Config; config != nil {
- for k, v := range config.Labels {
- if _, exists := s.Labels[k]; !exists {
- s.Labels[k] = v
- }
+ for k, v := range labels {
+ if _, exists := s.Labels[k]; !exists {
+ s.Labels[k] = v
}
}
// annotations
+
+ // Add annotations from the image
+ annotations, err := newImage.Annotations(ctx)
+ if err != nil {
+ return err
+ }
+ for k, v := range annotations {
+ annotations[k] = v
+ }
+
// in the event this container is in a pod, and the pod has an infra container
// we will want to configure it as a type "container" instead defaulting to
// the behavior of a "sandbox" container
@@ -59,36 +98,25 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
// VM, which is the default behavior
// - "container" denotes the container should join the VM of the SandboxID
// (the infra container)
- s.Annotations = make(map[string]string)
+
if len(s.Pod) > 0 {
- s.Annotations[ann.SandboxID] = s.Pod
- s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer
- }
- //
- // Next, add annotations from the image
- annotations, err := newImage.Annotations(ctx)
- if err != nil {
- return err
+ annotations[ann.SandboxID] = s.Pod
+ annotations[ann.ContainerType] = ann.ContainerTypeContainer
}
- for k, v := range annotations {
+
+ // now pass in the values from client
+ for k, v := range s.Annotations {
annotations[k] = v
}
+ s.Annotations = annotations
- // entrypoint
- if config := newImage.Config; config != nil {
- if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 {
- s.Entrypoint = config.Entrypoint
- }
- if len(s.Command) < 1 && len(config.Cmd) > 0 {
- s.Command = config.Cmd
- }
- if len(s.Command) < 1 && len(s.Entrypoint) < 1 {
- return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image")
- }
- // workdir
- if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 {
- s.WorkDir = config.WorkingDir
- }
+ // workdir
+ workingDir, err := newImage.WorkingDir(ctx)
+ if err != nil {
+ return err
+ }
+ if len(s.WorkDir) < 1 && len(workingDir) > 1 {
+ s.WorkDir = workingDir
}
if len(s.SeccompProfilePath) < 1 {
@@ -99,15 +127,10 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
s.SeccompProfilePath = p
}
- if user := s.User; len(user) == 0 {
- switch {
- // TODO This should be enabled when namespaces actually work
- //case usernsMode.IsKeepID():
- // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
- case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0):
- s.User = "0"
- default:
- s.User = newImage.Config.User
+ if len(s.User) == 0 {
+ s.User, err = newImage.User(ctx)
+ if err != nil {
+ return err
}
}
if err := finishThrottleDevices(s); err != nil {
@@ -116,7 +139,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat
// Unless already set via the CLI, check if we need to disable process
// labels or set the defaults.
if len(s.SelinuxOpts) == 0 {
- if err := SetLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil {
+ if err := setLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil {
return err
}
}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
index 264e0ff8e..01ddcf9c8 100644
--- a/pkg/specgen/generate/container_create.go
+++ b/pkg/specgen/generate/container_create.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/storage"
"github.com/pkg/errors"
@@ -14,40 +15,108 @@ import (
)
// MakeContainer creates a container based on the SpecGenerator
-func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
+func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
+ rtc, err := rt.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ // If joining a pod, retrieve the pod for use.
+ var pod *libpod.Pod
+ if s.Pod != "" {
+ foundPod, err := rt.LookupPod(s.Pod)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving pod %s", s.Pod)
+ }
+ pod = foundPod
+ }
+
+ // Set defaults for unset namespaces
+ if s.PidNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("pid", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.PidNS = defaultNS
+ }
+ if s.IpcNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("ipc", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.IpcNS = defaultNS
+ }
+ if s.UtsNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("uts", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.UtsNS = defaultNS
+ }
+ if s.UserNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("user", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.UserNS = defaultNS
+ }
+ if s.NetNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("net", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.NetNS = defaultNS
+ }
+ if s.CgroupNS.IsDefault() {
+ defaultNS, err := GetDefaultNamespaceMode("cgroup", rtc, pod)
+ if err != nil {
+ return nil, err
+ }
+ s.CgroupNS = defaultNS
+ }
+
+ options := []libpod.CtrCreateOption{}
+ options = append(options, libpod.WithCreateCommand())
+
+ var newImage *image.Image
+ if s.Rootfs != "" {
+ options = append(options, libpod.WithRootFS(s.Rootfs))
+ } else {
+ newImage, err = rt.ImageRuntime().NewFromLocal(s.Image)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
+ }
if err := s.Validate(); err != nil {
return nil, errors.Wrap(err, "invalid config provided")
}
- rtc, err := rt.GetConfig()
+
+ finalMounts, finalVolumes, err := finalizeMounts(ctx, s, rt, rtc, newImage)
if err != nil {
return nil, err
}
- options, err := createContainerOptions(rt, s)
+ opts, err := createContainerOptions(rt, s, pod, finalVolumes)
if err != nil {
return nil, err
}
+ options = append(options, opts...)
podmanPath, err := os.Executable()
if err != nil {
return nil, err
}
options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath))
- newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
- if err != nil {
- return nil, err
- }
-
- options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
- runtimeSpec, err := SpecGenToOCI(s, rt, newImage)
+ runtimeSpec, err := SpecGenToOCI(ctx, s, rt, rtc, newImage, finalMounts)
if err != nil {
return nil, err
}
- return rt.NewContainer(context.Background(), runtimeSpec, options...)
+ return rt.NewContainer(ctx, runtimeSpec, options...)
}
-func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) {
+func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *libpod.Pod, volumes []*specgen.NamedVolume) ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
var err error
@@ -74,21 +143,21 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib
for _, mount := range s.Mounts {
destinations = append(destinations, mount.Destination)
}
- for _, volume := range s.Volumes {
+ for _, volume := range volumes {
destinations = append(destinations, volume.Dest)
}
options = append(options, libpod.WithUserVolumes(destinations))
- if len(s.Volumes) != 0 {
- var volumes []*libpod.ContainerNamedVolume
- for _, v := range s.Volumes {
- volumes = append(volumes, &libpod.ContainerNamedVolume{
+ if len(volumes) != 0 {
+ var vols []*libpod.ContainerNamedVolume
+ for _, v := range volumes {
+ vols = append(vols, &libpod.ContainerNamedVolume{
Name: v.Name,
Dest: v.Dest,
Options: v.Options,
})
}
- options = append(options, libpod.WithNamedVolumes(volumes))
+ options = append(options, libpod.WithNamedVolumes(vols))
}
if len(s.Command) != 0 {
@@ -123,7 +192,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib
options = append(options, libpod.WithPrivileged(s.Privileged))
// Get namespace related options
- namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt)
+ namespaceOptions, err := GenerateNamespaceOptions(s, rt, pod)
if err != nil {
return nil, err
}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index cdd7d86da..a8b74b504 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -2,317 +2,408 @@ package generate
import (
"os"
+ "strings"
- "github.com/containers/common/pkg/capabilities"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
- "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/cgroups"
+ "github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen"
- "github.com/cri-o/ocicni/pkg/ocicni"
+ "github.com/containers/libpod/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-func GenerateNamespaceContainerOpts(s *specgen.SpecGenerator, rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
- var portBindings []ocicni.PortMapping
- options := make([]libpod.CtrCreateOption, 0)
+// Get the default namespace mode for any given namespace type.
+func GetDefaultNamespaceMode(nsType string, cfg *config.Config, pod *libpod.Pod) (specgen.Namespace, error) {
+ // The default for most is private
+ toReturn := specgen.Namespace{}
+ toReturn.NSMode = specgen.Private
- // Cgroups
- switch {
- case s.CgroupNS.IsPrivate():
- ns := s.CgroupNS.Value
- if _, err := os.Stat(ns); err != nil {
- return nil, err
+ // Ensure case insensitivity
+ nsType = strings.ToLower(nsType)
+
+ // If the pod is not nil - check shared namespaces
+ if pod != nil && pod.HasInfraContainer() {
+ podMode := false
+ switch {
+ case nsType == "pid" && pod.SharesPID():
+ podMode = true
+ case nsType == "ipc" && pod.SharesIPC():
+ podMode = true
+ case nsType == "uts" && pod.SharesUTS():
+ podMode = true
+ case nsType == "user" && pod.SharesUser():
+ podMode = true
+ case nsType == "net" && pod.SharesNet():
+ podMode = true
+ case nsType == "cgroup" && pod.SharesCgroup():
+ podMode = true
}
- case s.CgroupNS.IsContainer():
- connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value)
- if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value)
+ if podMode {
+ toReturn.NSMode = specgen.FromPod
+ return toReturn, nil
}
- options = append(options, libpod.WithCgroupNSFrom(connectedCtr))
- // TODO
- //default:
- // return nil, errors.New("cgroup name only supports private and container")
}
- if s.CgroupParent != "" {
- options = append(options, libpod.WithCgroupParent(s.CgroupParent))
+ // If we have containers.conf and are not using cgroupns, use that.
+ if cfg != nil && nsType != "cgroup" {
+ switch nsType {
+ case "pid":
+ return specgen.ParseNamespace(cfg.Containers.PidNS)
+ case "ipc":
+ return specgen.ParseNamespace(cfg.Containers.IPCNS)
+ case "uts":
+ return specgen.ParseNamespace(cfg.Containers.UTSNS)
+ case "user":
+ return specgen.ParseUserNamespace(cfg.Containers.UserNS)
+ case "net":
+ ns, _, err := specgen.ParseNetworkNamespace(cfg.Containers.NetNS)
+ return ns, err
+ }
}
- if s.CgroupsMode != "" {
- options = append(options, libpod.WithCgroupsMode(s.CgroupsMode))
+ switch nsType {
+ case "pid", "ipc", "uts":
+ // PID, IPC, UTS both default to private, do nothing
+ case "user":
+ // User namespace always defaults to host
+ toReturn.NSMode = specgen.Host
+ case "net":
+ // Net defaults to Slirp on rootless, Bridge otherwise.
+ if rootless.IsRootless() {
+ toReturn.NSMode = specgen.Slirp
+ } else {
+ toReturn.NSMode = specgen.Bridge
+ }
+ case "cgroup":
+ // Cgroup is host for v1, private for v2.
+ // We can't trust c/common for this, as it only assumes private.
+ cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
+ if err != nil {
+ return toReturn, err
+ }
+ if !cgroupsv2 {
+ toReturn.NSMode = specgen.Host
+ }
+ default:
+ return toReturn, errors.Wrapf(define.ErrInvalidArg, "invalid namespace type %s passed", nsType)
}
- // ipc
- switch {
- case s.IpcNS.IsHost():
- options = append(options, libpod.WithShmDir("/dev/shm"))
- case s.IpcNS.IsContainer():
- connectedCtr, err := rt.LookupContainer(s.IpcNS.Value)
+ return toReturn, nil
+}
+
+// GenerateNamespaceOptions generates container creation options for all
+// namespaces in a SpecGenerator.
+// Pod is the pod the container will join. May be nil is the container is not
+// joining a pod.
+// TODO: Consider grouping options that are not directly attached to a namespace
+// elsewhere.
+func GenerateNamespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) {
+ toReturn := []libpod.CtrCreateOption{}
+
+ // If pod is not nil, get infra container.
+ var infraCtr *libpod.Container
+ if pod != nil {
+ infraID, err := pod.InfraContainerID()
if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value)
+ // This is likely to be of the fatal kind (pod was
+ // removed) so hard fail
+ return nil, errors.Wrapf(err, "error looking up pod %s infra container", pod.ID())
+ }
+ if infraID != "" {
+ ctr, err := rt.GetContainer(infraID)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error retrieving pod %s infra container %s", pod.ID(), infraID)
+ }
+ infraCtr = ctr
}
- options = append(options, libpod.WithIPCNSFrom(connectedCtr))
- options = append(options, libpod.WithShmDir(connectedCtr.ShmDir()))
}
- // pid
- if s.PidNS.IsContainer() {
- connectedCtr, err := rt.LookupContainer(s.PidNS.Value)
+ errNoInfra := errors.Wrapf(define.ErrInvalidArg, "cannot use pod namespace as container is not joining a pod or pod has no infra container")
+
+ // PID
+ switch s.PidNS.NSMode {
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
+ }
+ toReturn = append(toReturn, libpod.WithPIDNSFrom(infraCtr))
+ case specgen.FromContainer:
+ pidCtr, err := rt.LookupContainer(s.PidNS.Value)
if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value)
+ return nil, errors.Wrapf(err, "error looking up container to share pid namespace with")
}
- options = append(options, libpod.WithPIDNSFrom(connectedCtr))
+ toReturn = append(toReturn, libpod.WithPIDNSFrom(pidCtr))
}
- // uts
- switch {
- case s.UtsNS.IsPod():
- connectedPod, err := rt.LookupPod(s.UtsNS.Value)
- if err != nil {
- return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value)
+ // IPC
+ switch s.IpcNS.NSMode {
+ case specgen.Host:
+ // Force use of host /dev/shm for host namespace
+ toReturn = append(toReturn, libpod.WithShmDir("/dev/shm"))
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
}
- options = append(options, libpod.WithUTSNSFromPod(connectedPod))
- case s.UtsNS.IsContainer():
- connectedCtr, err := rt.LookupContainer(s.UtsNS.Value)
+ toReturn = append(toReturn, libpod.WithIPCNSFrom(infraCtr))
+ case specgen.FromContainer:
+ ipcCtr, err := rt.LookupContainer(s.IpcNS.Value)
if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value)
+ return nil, errors.Wrapf(err, "error looking up container to share ipc namespace with")
}
-
- options = append(options, libpod.WithUTSNSFrom(connectedCtr))
+ toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr))
+ toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir()))
}
- if s.UseImageHosts {
- options = append(options, libpod.WithUseImageHosts())
- } else if len(s.HostAdd) > 0 {
- options = append(options, libpod.WithHosts(s.HostAdd))
+ // UTS
+ switch s.UtsNS.NSMode {
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
+ }
+ toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr))
+ case specgen.FromContainer:
+ utsCtr, err := rt.LookupContainer(s.UtsNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "error looking up container to share uts namespace with")
+ }
+ toReturn = append(toReturn, libpod.WithUTSNSFrom(utsCtr))
}
// User
-
- switch {
- case s.UserNS.IsPath():
- ns := s.UserNS.Value
- if ns == "" {
- return nil, errors.Errorf("invalid empty user-defined user namespace")
- }
- _, err := os.Stat(ns)
- if err != nil {
- return nil, err
+ switch s.UserNS.NSMode {
+ case specgen.KeepID:
+ if rootless.IsRootless() {
+ s.User = ""
+ } else {
+ // keep-id as root doesn't need a user namespace
+ s.UserNS.NSMode = specgen.Host
}
- if s.IDMappings != nil {
- options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
}
- case s.UserNS.IsContainer():
- connectedCtr, err := rt.LookupContainer(s.UserNS.Value)
+ toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr))
+ case specgen.FromContainer:
+ userCtr, err := rt.LookupContainer(s.UserNS.Value)
if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value)
- }
- options = append(options, libpod.WithUserNSFrom(connectedCtr))
- default:
- if s.IDMappings != nil {
- options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ return nil, errors.Wrapf(err, "error looking up container to share user namespace with")
}
+ toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr))
}
- options = append(options, libpod.WithUser(s.User))
- options = append(options, libpod.WithGroups(s.Groups))
-
- if len(s.PortMappings) > 0 {
- portBindings = s.PortMappings
+ if s.IDMappings != nil {
+ toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings))
+ }
+ if s.User != "" {
+ toReturn = append(toReturn, libpod.WithUser(s.User))
+ }
+ if len(s.Groups) > 0 {
+ toReturn = append(toReturn, libpod.WithGroups(s.Groups))
}
- switch {
- case s.NetNS.IsPath():
- ns := s.NetNS.Value
- if ns == "" {
- return nil, errors.Errorf("invalid empty user-defined network namespace")
+ // Cgroup
+ switch s.CgroupNS.NSMode {
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
}
- _, err := os.Stat(ns)
+ toReturn = append(toReturn, libpod.WithCgroupNSFrom(infraCtr))
+ case specgen.FromContainer:
+ cgroupCtr, err := rt.LookupContainer(s.CgroupNS.Value)
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "error looking up container to share cgroup namespace with")
}
- case s.NetNS.IsContainer():
- connectedCtr, err := rt.LookupContainer(s.NetNS.Value)
+ toReturn = append(toReturn, libpod.WithCgroupNSFrom(cgroupCtr))
+ }
+
+ if s.CgroupParent != "" {
+ toReturn = append(toReturn, libpod.WithCgroupParent(s.CgroupParent))
+ }
+
+ if s.CgroupsMode != "" {
+ toReturn = append(toReturn, libpod.WithCgroupsMode(s.CgroupsMode))
+ }
+
+ // Net
+ // TODO image ports
+ // TODO validate CNINetworks, StaticIP, StaticIPv6 are only set if we
+ // are in bridge mode.
+ postConfigureNetNS := !s.UserNS.IsHost()
+ switch s.NetNS.NSMode {
+ case specgen.FromPod:
+ if pod == nil || infraCtr == nil {
+ return nil, errNoInfra
+ }
+ toReturn = append(toReturn, libpod.WithNetNSFrom(infraCtr))
+ case specgen.FromContainer:
+ netCtr, err := rt.LookupContainer(s.NetNS.Value)
if err != nil {
- return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value)
+ return nil, errors.Wrapf(err, "error looking up container to share net namespace with")
}
- options = append(options, libpod.WithNetNSFrom(connectedCtr))
- case !s.NetNS.IsHost() && s.NetNS.NSMode != specgen.NoNetwork:
- postConfigureNetNS := !s.UserNS.IsHost()
- options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks))
+ toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr))
+ case specgen.Slirp:
+ toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "slirp4netns", nil))
+ case specgen.Bridge:
+ toReturn = append(toReturn, libpod.WithNetNS(s.PortMappings, postConfigureNetNS, "bridge", s.CNINetworks))
}
- if len(s.DNSSearch) > 0 {
- options = append(options, libpod.WithDNSSearch(s.DNSSearch))
+ if s.UseImageHosts {
+ toReturn = append(toReturn, libpod.WithUseImageHosts())
+ } else if len(s.HostAdd) > 0 {
+ toReturn = append(toReturn, libpod.WithHosts(s.HostAdd))
}
- if len(s.DNSServer) > 0 {
- // TODO I'm not sure how we are going to handle this given the input
- if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" {
- options = append(options, libpod.WithUseImageResolvConf())
- } else {
- var dnsServers []string
- for _, d := range s.DNSServer {
- dnsServers = append(dnsServers, d.String())
- }
- options = append(options, libpod.WithDNS(dnsServers))
+ if len(s.DNSSearch) > 0 {
+ toReturn = append(toReturn, libpod.WithDNSSearch(s.DNSSearch))
+ }
+ if s.UseImageResolvConf {
+ toReturn = append(toReturn, libpod.WithUseImageResolvConf())
+ } else if len(s.DNSServers) > 0 {
+ var dnsServers []string
+ for _, d := range s.DNSServers {
+ dnsServers = append(dnsServers, d.String())
}
+ toReturn = append(toReturn, libpod.WithDNS(dnsServers))
}
- if len(s.DNSOption) > 0 {
- options = append(options, libpod.WithDNSOption(s.DNSOption))
+ if len(s.DNSOptions) > 0 {
+ toReturn = append(toReturn, libpod.WithDNSOption(s.DNSOptions))
}
if s.StaticIP != nil {
- options = append(options, libpod.WithStaticIP(*s.StaticIP))
+ toReturn = append(toReturn, libpod.WithStaticIP(*s.StaticIP))
}
-
if s.StaticMAC != nil {
- options = append(options, libpod.WithStaticMAC(*s.StaticMAC))
+ toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC))
}
- return options, nil
+
+ return toReturn, nil
}
-func pidConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
- if s.PidNS.IsPath() {
- return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value)
- }
- if s.PidNS.IsHost() {
- return g.RemoveLinuxNamespace(string(spec.PIDNamespace))
+func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt *libpod.Runtime) error {
+ // PID
+ switch s.PidNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.PidNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified PID namespace path %q", s.PidNS.Value)
+ }
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value); err != nil {
+ return err
+ }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.PIDNamespace)); err != nil {
+ return err
+ }
+ case specgen.Private:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), ""); err != nil {
+ return err
+ }
}
- if s.PidNS.IsContainer() {
- logrus.Debugf("using container %s pidmode", s.PidNS.Value)
+
+ // IPC
+ switch s.IpcNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.IpcNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified IPC namespace path %q", s.IpcNS.Value)
+ }
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value); err != nil {
+ return err
+ }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.IPCNamespace)); err != nil {
+ return err
+ }
+ case specgen.Private:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), ""); err != nil {
+ return err
+ }
}
- if s.PidNS.IsPod() {
- logrus.Debug("using pod pidmode")
+
+ // UTS
+ switch s.UtsNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.UtsNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified UTS namespace path %q", s.UtsNS.Value)
+ }
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value); err != nil {
+ return err
+ }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.UTSNamespace)); err != nil {
+ return err
+ }
+ case specgen.Private:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), ""); err != nil {
+ return err
+ }
}
- return nil
-}
-func utsConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, runtime *libpod.Runtime) error {
hostname := s.Hostname
- var err error
if hostname == "" {
switch {
- case s.UtsNS.IsContainer():
- utsCtr, err := runtime.LookupContainer(s.UtsNS.Value)
+ case s.UtsNS.NSMode == specgen.FromContainer:
+ utsCtr, err := rt.LookupContainer(s.UtsNS.Value)
if err != nil {
- return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value)
+ return errors.Wrapf(err, "error looking up container to share uts namespace with")
}
hostname = utsCtr.Hostname()
- case s.NetNS.IsHost() || s.UtsNS.IsHost():
- hostname, err = os.Hostname()
+ case s.NetNS.NSMode == specgen.Host || s.UtsNS.NSMode == specgen.Host:
+ tmpHostname, err := os.Hostname()
if err != nil {
return errors.Wrap(err, "unable to retrieve hostname of the host")
}
+ hostname = tmpHostname
default:
logrus.Debug("No hostname set; container's hostname will default to runtime default")
}
}
+
g.RemoveHostname()
- if s.Hostname != "" || !s.UtsNS.IsHost() {
- // Set the hostname in the OCI configuration only
- // if specified by the user or if we are creating
- // a new UTS namespace.
+ if s.Hostname != "" || s.UtsNS.NSMode != specgen.Host {
+ // Set the hostname in the OCI configuration only if specified by
+ // the user or if we are creating a new UTS namespace.
+ // TODO: Should we be doing this for pod or container shared
+ // namespaces?
g.SetHostname(hostname)
}
g.AddProcessEnv("HOSTNAME", hostname)
- if s.UtsNS.IsPath() {
- return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value)
- }
- if s.UtsNS.IsHost() {
- return g.RemoveLinuxNamespace(string(spec.UTSNamespace))
- }
- if s.UtsNS.IsContainer() {
- logrus.Debugf("using container %s utsmode", s.UtsNS.Value)
- }
- return nil
-}
-
-func ipcConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
- if s.IpcNS.IsPath() {
- return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value)
- }
- if s.IpcNS.IsHost() {
- return g.RemoveLinuxNamespace(s.IpcNS.Value)
- }
- if s.IpcNS.IsContainer() {
- logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value)
- }
- return nil
-}
-
-func cgroupConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
- if s.CgroupNS.IsPath() {
- return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value)
- }
- if s.CgroupNS.IsHost() {
- return g.RemoveLinuxNamespace(s.CgroupNS.Value)
- }
- if s.CgroupNS.IsPrivate() {
- return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "")
- }
- if s.CgroupNS.IsContainer() {
- logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value)
- }
- return nil
-}
-
-func networkConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
- switch {
- case s.NetNS.IsHost():
- logrus.Debug("Using host netmode")
- if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
- return err
- }
-
- case s.NetNS.NSMode == specgen.NoNetwork:
- logrus.Debug("Using none netmode")
- case s.NetNS.NSMode == specgen.Bridge:
- logrus.Debug("Using bridge netmode")
- case s.NetNS.IsContainer():
- logrus.Debugf("using container %s netmode", s.NetNS.Value)
- case s.NetNS.IsPath():
- logrus.Debug("Using ns netmode")
- if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil {
- return err
+ // User
+ switch s.UserNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.UserNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified user namespace path %s", s.UserNS.Value)
}
- case s.NetNS.IsPod():
- logrus.Debug("Using pod netmode, unless pod is not sharing")
- case s.NetNS.NSMode == specgen.Slirp:
- logrus.Debug("Using slirp4netns netmode")
- default:
- return errors.Errorf("unknown network mode")
- }
-
- if g.Config.Annotations == nil {
- g.Config.Annotations = make(map[string]string)
- }
-
- if s.PublishImagePorts {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
- } else {
- g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
- }
-
- return nil
-}
-
-func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
- if s.UserNS.IsPath() {
if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil {
return err
}
// runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping
g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1))
g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1))
- }
-
- if s.IDMappings != nil {
- if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() {
- if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
- return err
- }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil {
+ return err
+ }
+ case specgen.KeepID:
+ var (
+ err error
+ uid, gid int
+ )
+ s.IDMappings, uid, gid, err = util.GetKeepIDMapping()
+ if err != nil {
+ return err
+ }
+ g.SetProcessUID(uint32(uid))
+ g.SetProcessGID(uint32(gid))
+ fallthrough
+ case specgen.Private:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
+ return err
+ }
+ if s.IDMappings == nil || (len(s.IDMappings.UIDMap) == 0 && len(s.IDMappings.GIDMap) == 0) {
+ return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace")
}
for _, uidmap := range s.IDMappings.UIDMap {
g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
@@ -321,64 +412,52 @@ func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) err
g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
}
}
- return nil
-}
-
-func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error {
- // HANDLE CAPABILITIES
- // NOTE: Must happen before SECCOMP
- if s.Privileged {
- g.SetupPrivileged(true)
- }
- useNotRoot := func(user string) bool {
- if user == "" || user == "root" || user == "0" {
- return false
+ // Cgroup
+ switch s.CgroupNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.CgroupNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified cgroup namespace path %s", s.CgroupNS.Value)
}
- return true
- }
- configSpec := g.Config
- var err error
- var caplist []string
- bounding := configSpec.Process.Capabilities.Bounding
- if useNotRoot(s.User) {
- configSpec.Process.Capabilities.Bounding = caplist
- }
- caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
- if err != nil {
- return err
- }
-
- configSpec.Process.Capabilities.Bounding = caplist
- configSpec.Process.Capabilities.Permitted = caplist
- configSpec.Process.Capabilities.Inheritable = caplist
- configSpec.Process.Capabilities.Effective = caplist
- configSpec.Process.Capabilities.Ambient = caplist
- if useNotRoot(s.User) {
- caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
- if err != nil {
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value); err != nil {
+ return err
+ }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.CgroupNamespace)); err != nil {
+ return err
+ }
+ case specgen.Private:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), ""); err != nil {
return err
}
}
- configSpec.Process.Capabilities.Bounding = caplist
- // HANDLE SECCOMP
- if s.SeccompProfilePath != "unconfined" {
- seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
- if err != nil {
+ // Net
+ switch s.NetNS.NSMode {
+ case specgen.Path:
+ if _, err := os.Stat(s.NetNS.Value); err != nil {
+ return errors.Wrapf(err, "cannot find specified network namespace path %s", s.NetNS.Value)
+ }
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil {
+ return err
+ }
+ case specgen.Host:
+ if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
+ return err
+ }
+ case specgen.Private, specgen.NoNetwork:
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), ""); err != nil {
return err
}
- configSpec.Linux.Seccomp = seccompConfig
}
- // Clear default Seccomp profile from Generator for privileged containers
- if s.SeccompProfilePath == "unconfined" || s.Privileged {
- configSpec.Linux.Seccomp = nil
+ if g.Config.Annotations == nil {
+ g.Config.Annotations = make(map[string]string)
}
-
- g.SetRootReadonly(s.ReadOnlyFilesystem)
- for sysctlKey, sysctlVal := range s.Sysctl {
- g.AddLinuxSysctl(sysctlKey, sysctlVal)
+ if s.PublishImagePorts {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ } else {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
}
return nil
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
index 0ed091f9a..87262684e 100644
--- a/pkg/specgen/generate/oci.go
+++ b/pkg/specgen/generate/oci.go
@@ -1,8 +1,10 @@
package generate
import (
+ "context"
"strings"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
@@ -10,9 +12,90 @@ import (
"github.com/opencontainers/runc/libcontainer/user"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
+ "github.com/pkg/errors"
)
-func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
+func addRlimits(s *specgen.SpecGenerator, g *generate.Generator) error {
+ var (
+ kernelMax uint64 = 1048576
+ isRootless = rootless.IsRootless()
+ nofileSet = false
+ nprocSet = false
+ )
+
+ if s.Rlimits == nil {
+ g.Config.Process.Rlimits = nil
+ return nil
+ }
+
+ for _, u := range s.Rlimits {
+ name := "RLIMIT_" + strings.ToUpper(u.Type)
+ if name == "RLIMIT_NOFILE" {
+ nofileSet = true
+ } else if name == "RLIMIT_NPROC" {
+ nprocSet = true
+ }
+ g.AddProcessRlimits(name, u.Hard, u.Soft)
+ }
+
+ // If not explicitly overridden by the user, default number of open
+ // files and number of processes to the maximum they can be set to
+ // (without overriding a sysctl)
+ if !nofileSet && !isRootless {
+ g.AddProcessRlimits("RLIMIT_NOFILE", kernelMax, kernelMax)
+ }
+ if !nprocSet && !isRootless {
+ g.AddProcessRlimits("RLIMIT_NPROC", kernelMax, kernelMax)
+ }
+
+ return nil
+}
+
+// Produce the final command for the container.
+func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image, rtc *config.Config) ([]string, error) {
+ finalCommand := []string{}
+
+ entrypoint := s.Entrypoint
+ if len(entrypoint) == 0 && img != nil {
+ newEntry, err := img.Entrypoint(ctx)
+ if err != nil {
+ return nil, err
+ }
+ entrypoint = newEntry
+ }
+
+ finalCommand = append(finalCommand, entrypoint...)
+
+ command := s.Command
+ if len(command) == 0 && img != nil {
+ newCmd, err := img.Cmd(ctx)
+ if err != nil {
+ return nil, err
+ }
+ command = newCmd
+ }
+
+ finalCommand = append(finalCommand, command...)
+
+ if len(finalCommand) == 0 {
+ return nil, errors.Errorf("no command or entrypoint provided, and no CMD or ENTRYPOINT from image")
+ }
+
+ if s.Init {
+ initPath := s.InitPath
+ if initPath == "" && rtc != nil {
+ initPath = rtc.Engine.InitPath
+ }
+ if initPath == "" {
+ return nil, errors.Errorf("no path to init binary found but container requested an init")
+ }
+ finalCommand = append([]string{initPath, "--"}, finalCommand...)
+ }
+
+ return finalCommand, nil
+}
+
+func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, newImage *image.Image, mounts []spec.Mount) (*spec.Spec, error) {
var (
inUserNS bool
)
@@ -137,7 +220,13 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
g.AddMount(cgroupMnt)
}
g.SetProcessCwd(s.WorkDir)
- g.SetProcessArgs(s.Command)
+
+ finalCmd, err := makeCommand(ctx, s, newImage, rtc)
+ if err != nil {
+ return nil, err
+ }
+ g.SetProcessArgs(finalCmd)
+
g.SetProcessTerminal(s.Terminal)
for key, val := range s.Annotations {
@@ -176,35 +265,12 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
g.AddProcessEnv(name, val)
}
- // TODO rlimits and ulimits needs further refinement by someone more
- // familiar with the code.
- //if err := addRlimits(config, &g); err != nil {
- // return nil, err
- //}
-
- // NAMESPACES
-
- if err := pidConfigureGenerator(s, &g); err != nil {
- return nil, err
- }
-
- if err := userConfigureGenerator(s, &g); err != nil {
- return nil, err
- }
-
- if err := networkConfigureGenerator(s, &g); err != nil {
+ if err := addRlimits(s, &g); err != nil {
return nil, err
}
- if err := utsConfigureGenerator(s, &g, rt); err != nil {
- return nil, err
- }
-
- if err := ipcConfigureGenerator(s, &g); err != nil {
- return nil, err
- }
-
- if err := cgroupConfigureGenerator(s, &g); err != nil {
+ // NAMESPACES
+ if err := specConfigureNamespaces(s, &g, rt); err != nil {
return nil, err
}
configSpec := g.Config
@@ -214,7 +280,7 @@ func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.
}
// BIND MOUNTS
- configSpec.Mounts = SupercedeUserMounts(s.Mounts, configSpec.Mounts)
+ configSpec.Mounts = SupercedeUserMounts(mounts, configSpec.Mounts)
// Process mounts to ensure correct options
if err := InitFSMounts(configSpec.Mounts); err != nil {
return nil, err
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
index 292f9b155..babfba9bc 100644
--- a/pkg/specgen/generate/pod_create.go
+++ b/pkg/specgen/generate/pod_create.go
@@ -46,6 +46,13 @@ func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, er
if len(p.HostAdd) > 0 {
options = append(options, libpod.WithPodHosts(p.HostAdd))
}
+ if len(p.DNSServer) > 0 {
+ var dnsServers []string
+ for _, d := range p.DNSServer {
+ dnsServers = append(dnsServers, d.String())
+ }
+ options = append(options, libpod.WithPodDNS(dnsServers))
+ }
if len(p.DNSOption) > 0 {
options = append(options, libpod.WithPodDNSOption(p.DNSOption))
}
diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go
index ef4b3b47a..e2da9e976 100644
--- a/pkg/specgen/generate/security.go
+++ b/pkg/specgen/generate/security.go
@@ -1,15 +1,22 @@
package generate
import (
+ "strings"
+
+ "github.com/containers/common/pkg/capabilities"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/pkg/util"
+ "github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
-// SetLabelOpts sets the label options of the SecurityConfig according to the
+// setLabelOpts sets the label options of the SecurityConfig according to the
// input.
-func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error {
+func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error {
if !runtime.EnableLabeling() || s.Privileged {
s.SelinuxOpts = label.DisableSecOpt()
return nil
@@ -48,12 +55,10 @@ func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s
return nil
}
-// ConfigureGenerator configures the generator according to the input.
-/*
-func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error {
+func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error {
// HANDLE CAPABILITIES
// NOTE: Must happen before SECCOMP
- if c.Privileged {
+ if s.Privileged {
g.SetupPrivileged(true)
}
@@ -63,56 +68,66 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon
}
return true
}
-
configSpec := g.Config
var err error
- var defaultCaplist []string
+ var caplist []string
bounding := configSpec.Process.Capabilities.Bounding
- if useNotRoot(user.User) {
- configSpec.Process.Capabilities.Bounding = defaultCaplist
+ if useNotRoot(s.User) {
+ configSpec.Process.Capabilities.Bounding = caplist
}
- defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop)
+ caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
if err != nil {
return err
}
+ privCapsRequired := []string{}
+
+ // If the container image specifies an label with a
+ // capabilities.ContainerImageLabel then split the comma separated list
+ // of capabilities and record them. This list indicates the only
+ // capabilities, required to run the container.
+ var capsRequiredRequested []string
+ for key, val := range s.Labels {
+ if util.StringInSlice(key, capabilities.ContainerImageLabels) {
+ capsRequiredRequested = strings.Split(val, ",")
+ }
+ }
+ if !s.Privileged && len(capsRequiredRequested) > 0 {
- privCapRequired := []string{}
-
- if !c.Privileged && len(c.CapRequired) > 0 {
- // Pass CapRequired in CapAdd field to normalize capabilities names
- capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil)
+ // Pass capRequiredRequested in CapAdd field to normalize capabilities names
+ capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil)
if err != nil {
- logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ","))
+ logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ","))
} else {
- // Verify all capRequiered are in the defaultCapList
- for _, cap := range capRequired {
- if !util.StringInSlice(cap, defaultCaplist) {
- privCapRequired = append(privCapRequired, cap)
+ // Verify all capRequiered are in the capList
+ for _, cap := range capsRequired {
+ if !util.StringInSlice(cap, caplist) {
+ privCapsRequired = append(privCapsRequired, cap)
}
}
}
- if len(privCapRequired) == 0 {
- defaultCaplist = capRequired
+ if len(privCapsRequired) == 0 {
+ caplist = capsRequired
} else {
- logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ","))
+ logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ","))
}
}
- configSpec.Process.Capabilities.Bounding = defaultCaplist
- configSpec.Process.Capabilities.Permitted = defaultCaplist
- configSpec.Process.Capabilities.Inheritable = defaultCaplist
- configSpec.Process.Capabilities.Effective = defaultCaplist
- configSpec.Process.Capabilities.Ambient = defaultCaplist
- if useNotRoot(user.User) {
- defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop)
+
+ configSpec.Process.Capabilities.Bounding = caplist
+ configSpec.Process.Capabilities.Permitted = caplist
+ configSpec.Process.Capabilities.Inheritable = caplist
+ configSpec.Process.Capabilities.Effective = caplist
+ configSpec.Process.Capabilities.Ambient = caplist
+ if useNotRoot(s.User) {
+ caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
if err != nil {
return err
}
}
- configSpec.Process.Capabilities.Bounding = defaultCaplist
+ configSpec.Process.Capabilities.Bounding = caplist
// HANDLE SECCOMP
- if c.SeccompProfilePath != "unconfined" {
- seccompConfig, err := getSeccompConfig(c, configSpec)
+ if s.SeccompProfilePath != "unconfined" {
+ seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
if err != nil {
return err
}
@@ -120,35 +135,14 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon
}
// Clear default Seccomp profile from Generator for privileged containers
- if c.SeccompProfilePath == "unconfined" || c.Privileged {
+ if s.SeccompProfilePath == "unconfined" || s.Privileged {
configSpec.Linux.Seccomp = nil
}
- for _, opt := range c.SecurityOpts {
- // Split on both : and =
- splitOpt := strings.Split(opt, "=")
- if len(splitOpt) == 1 {
- splitOpt = strings.Split(opt, ":")
- }
- if len(splitOpt) < 2 {
- continue
- }
- switch splitOpt[0] {
- case "label":
- configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
- case "seccomp":
- configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
- case "apparmor":
- configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
- }
- }
-
- g.SetRootReadonly(c.ReadOnlyRootfs)
- for sysctlKey, sysctlVal := range c.Sysctl {
+ g.SetRootReadonly(s.ReadOnlyFilesystem)
+ for sysctlKey, sysctlVal := range s.Sysctl {
g.AddLinuxSysctl(sysctlKey, sysctlVal)
}
return nil
}
-
-*/
diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go
index c9a36ed46..241c9adeb 100644
--- a/pkg/specgen/generate/storage.go
+++ b/pkg/specgen/generate/storage.go
@@ -1,15 +1,16 @@
package generate
-//nolint
-
import (
+ "context"
"fmt"
+ "os"
"path"
"path/filepath"
"strings"
- "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -17,6 +18,7 @@ import (
"github.com/sirupsen/logrus"
)
+// TODO unify this in one place - maybe libpod/define
const (
// TypeBind is the type for mounting host dir
TypeBind = "bind"
@@ -27,788 +29,278 @@ const (
)
var (
- errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint
- optionArgError = errors.Errorf("must provide an argument for option") //nolint
- noDestError = errors.Errorf("must set volume destination") //nolint
+ errDuplicateDest = errors.Errorf("duplicate mount destination")
)
-// Parse all volume-related options in the create config into a set of mounts
-// and named volumes to add to the container.
-// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags.
-// TODO: Named volume options - should we default to rprivate? It bakes into a
-// bind mount under the hood...
-// TODO: handle options parsing/processing via containers/storage/pkg/mount
-func parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint
-
- // TODO this needs to come from the image and erquires a runtime
-
- // Add image volumes.
- //baseMounts, baseVolumes, err := config.getImageVolumes()
- //if err != nil {
- // return nil, nil, err
- //}
-
- // Add --volumes-from.
- // Overrides image volumes unconditionally.
- //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime)
- //if err != nil {
- // return nil, nil, err
- //}
- //for dest, mount := range vFromMounts {
- // baseMounts[dest] = mount
- //}
- //for dest, volume := range vFromVolumes {
- // baseVolumes[dest] = volume
- //}
-
- // Next mounts from the --mounts flag.
- // Do not override yet.
- //unifiedMounts, _, err := getMounts(mounts)
- //if err != nil {
- // return err
- //}
- //
- //// Next --volumes flag.
- //// Do not override yet.
- //volumeMounts, _ , err := getVolumeMounts(volMounts)
- //if err != nil {
- // return err
- //}
- //
- //// Next --tmpfs flag.
- //// Do not override yet.
- //tmpfsMounts, err := getTmpfsMounts(tmpMounts)
- //if err != nil {
- // return err
- //}
-
- //// Unify mounts from --mount, --volume, --tmpfs.
- //// Also add mounts + volumes directly from createconfig.
- //// Start with --volume.
- //for dest, mount := range volumeMounts {
- // if _, ok := unifiedMounts[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
- // }
- // unifiedMounts[dest] = mount
- //}
- //for dest, volume := range volumeVolumes {
- // if _, ok := unifiedVolumes[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
- // }
- // unifiedVolumes[dest] = volume
- //}
- //// Now --tmpfs
- //for dest, tmpfs := range tmpfsMounts {
- // if _, ok := unifiedMounts[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
- // }
- // unifiedMounts[dest] = tmpfs
- //}
- //// Now spec mounts and volumes
- //for _, mount := range config.Mounts {
- // dest := mount.Destination
- // if _, ok := unifiedMounts[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
- // }
- // unifiedMounts[dest] = mount
- //}
- //for _, volume := range config.NamedVolumes {
- // dest := volume.Dest
- // if _, ok := unifiedVolumes[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, dest)
- // }
- // unifiedVolumes[dest] = volume
- //}
- //
- //// If requested, add container init binary
- //if config.Init {
- // initPath := config.InitPath
- // if initPath == "" {
- // rtc, err := runtime.GetConfig()
- // if err != nil {
- // return nil, nil, err
- // }
- // initPath = rtc.Engine.InitPath
- // }
- // initMount, err := config.addContainerInitBinary(initPath)
- // if err != nil {
- // return nil, nil, err
- // }
- // if _, ok := unifiedMounts[initMount.Destination]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
- // }
- // unifiedMounts[initMount.Destination] = initMount
- //}
- //
- //// Before superseding, we need to find volume mounts which conflict with
- //// named volumes, and vice versa.
- //// We'll delete the conflicts here as we supersede.
- //for dest := range unifiedMounts {
- // if _, ok := baseVolumes[dest]; ok {
- // delete(baseVolumes, dest)
- // }
- //}
- //for dest := range unifiedVolumes {
- // if _, ok := baseMounts[dest]; ok {
- // delete(baseMounts, dest)
- // }
- //}
- //
- //// Supersede volumes-from/image volumes with unified volumes from above.
- //// This is an unconditional replacement.
- //for dest, mount := range unifiedMounts {
- // baseMounts[dest] = mount
- //}
- //for dest, volume := range unifiedVolumes {
- // baseVolumes[dest] = volume
- //}
- //
- //// If requested, add tmpfs filesystems for read-only containers.
- //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs {
- // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"}
- // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
- // for _, dest := range readonlyTmpfs {
- // if _, ok := baseMounts[dest]; ok {
- // continue
- // }
- // if _, ok := baseVolumes[dest]; ok {
- // continue
- // }
- // localOpts := options
- // if dest == "/run" {
- // localOpts = append(localOpts, "noexec", "size=65536k")
- // } else {
- // localOpts = append(localOpts, "exec")
- // }
- // baseMounts[dest] = spec.Mount{
- // Destination: dest,
- // Type: "tmpfs",
- // Source: "tmpfs",
- // Options: localOpts,
- // }
- // }
- //}
- //
- //// Check for conflicts between named volumes and mounts
- //for dest := range baseMounts {
- // if _, ok := baseVolumes[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
- // }
- //}
- //for dest := range baseVolumes {
- // if _, ok := baseMounts[dest]; ok {
- // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
- // }
- //}
- //
- //// Final step: maps to arrays
- //finalMounts := make([]spec.Mount, 0, len(baseMounts))
- //for _, mount := range baseMounts {
- // if mount.Type == TypeBind {
- // absSrc, err := filepath.Abs(mount.Source)
- // if err != nil {
- // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
- // }
- // mount.Source = absSrc
- // }
- // finalMounts = append(finalMounts, mount)
- //}
- //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes))
- //for _, volume := range baseVolumes {
- // finalVolumes = append(finalVolumes, volume)
- //}
-
- //return finalMounts, finalVolumes, nil
- return nil
-}
-
-// Parse volumes from - a set of containers whose volumes we will mount in.
-// Grab the containers, retrieve any user-created spec mounts and all named
-// volumes, and return a list of them.
-// Conflicts are resolved simply - the last container specified wins.
-// Container names may be suffixed by mount options after a colon.
-// TODO: We should clean these paths if possible
-// TODO deferred baude
-func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
- // Both of these are maps of mount destination to mount type.
- // We ensure that each destination is only mounted to once in this way.
- //finalMounts := make(map[string]spec.Mount)
- //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume)
- //
- //for _, vol := range config.VolumesFrom {
- // var (
- // options = []string{}
- // err error
- // splitVol = strings.SplitN(vol, ":", 2)
- // )
- // if len(splitVol) == 2 {
- // splitOpts := strings.Split(splitVol[1], ",")
- // for _, checkOpt := range splitOpts {
- // switch checkOpt {
- // case "z", "ro", "rw":
- // // Do nothing, these are valid options
- // default:
- // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1])
- // }
- // }
- //
- // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil {
- // return nil, nil, err
- // }
- // }
- // ctr, err := runtime.LookupContainer(splitVol[0])
- // if err != nil {
- // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
- // }
- //
- // logrus.Debugf("Adding volumes from container %s", ctr.ID())
- //
- // // Look up the container's user volumes. This gets us the
- // // destinations of all mounts the user added to the container.
- // userVolumesArr := ctr.UserVolumes()
- //
- // // We're going to need to access them a lot, so convert to a map
- // // to reduce looping.
- // // We'll also use the map to indicate if we missed any volumes along the way.
- // userVolumes := make(map[string]bool)
- // for _, dest := range userVolumesArr {
- // userVolumes[dest] = false
- // }
- //
- // // Now we get the container's spec and loop through its volumes
- // // and append them in if we can find them.
- // spec := ctr.Spec()
- // if spec == nil {
- // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
- // }
- // for _, mnt := range spec.Mounts {
- // if mnt.Type != TypeBind {
- // continue
- // }
- // if _, exists := userVolumes[mnt.Destination]; exists {
- // userVolumes[mnt.Destination] = true
- //
- // if len(options) != 0 {
- // mnt.Options = options
- // }
- //
- // if _, ok := finalMounts[mnt.Destination]; ok {
- // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
- // }
- // finalMounts[mnt.Destination] = mnt
- // }
- // }
- //
- // // We're done with the spec mounts. Add named volumes.
- // // Add these unconditionally - none of them are automatically
- // // part of the container, as some spec mounts are.
- // namedVolumes := ctr.NamedVolumes()
- // for _, namedVol := range namedVolumes {
- // if _, exists := userVolumes[namedVol.Dest]; exists {
- // userVolumes[namedVol.Dest] = true
- // }
- //
- // if len(options) != 0 {
- // namedVol.Options = options
- // }
- //
- // if _, ok := finalMounts[namedVol.Dest]; ok {
- // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
- // }
- // finalNamedVolumes[namedVol.Dest] = namedVol
- // }
- //
- // // Check if we missed any volumes
- // for volDest, found := range userVolumes {
- // if !found {
- // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
- // }
- // }
- //}
- //
- //return finalMounts, finalNamedVolumes, nil
- return nil, nil, nil
-}
+// Produce final mounts and named volumes for a container
+func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *image.Image) ([]spec.Mount, []*specgen.NamedVolume, error) {
+ // Get image volumes
+ baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s)
+ if err != nil {
+ return nil, nil, err
+ }
-// getMounts takes user-provided input from the --mount flag and creates OCI
-// spec mounts and Libpod named volumes.
-// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
-// podman run --mount type=tmpfs,target=/dev/shm ...
-// podman run --mount type=volume,source=test-volume, ...
-func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
- finalMounts := make(map[string]spec.Mount)
- finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume)
+ // Get volumes-from mounts
+ volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt)
+ if err != nil {
+ return nil, nil, err
+ }
- errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]")
+ // Supercede from --volumes-from.
+ for dest, mount := range volFromMounts {
+ baseMounts[dest] = mount
+ }
+ for dest, volume := range volFromVolumes {
+ baseVolumes[dest] = volume
+ }
- // TODO(vrothberg): the manual parsing can be replaced with a regular expression
- // to allow a more robust parsing of the mount format and to give
- // precise errors regarding supported format versus supported options.
- for _, mount := range mounts {
- arr := strings.SplitN(mount, ",", 2)
- if len(arr) < 2 {
- return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ // Need to make map forms of specgen mounts/volumes.
+ unifiedMounts := map[string]spec.Mount{}
+ unifiedVolumes := map[string]*specgen.NamedVolume{}
+ for _, m := range s.Mounts {
+ if _, ok := unifiedMounts[m.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", m.Destination)
}
- kv := strings.Split(arr[0], "=")
- // TODO: type is not explicitly required in Docker.
- // If not specified, it defaults to "volume".
- if len(kv) != 2 || kv[0] != "type" {
- return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount)
+ unifiedMounts[m.Destination] = m
+ }
+ for _, v := range s.Volumes {
+ if _, ok := unifiedVolumes[v.Dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest)
}
+ unifiedVolumes[v.Dest] = v
+ }
- tokens := strings.Split(arr[1], ",")
- switch kv[1] {
- case TypeBind:
- mount, err := getBindMount(tokens)
- if err != nil {
- return nil, nil, err
- }
- if _, ok := finalMounts[mount.Destination]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
- }
- finalMounts[mount.Destination] = mount
- case TypeTmpfs:
- mount, err := getTmpfsMount(tokens)
- if err != nil {
- return nil, nil, err
- }
- if _, ok := finalMounts[mount.Destination]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination)
- }
- finalMounts[mount.Destination] = mount
- case "volume":
- volume, err := getNamedVolume(tokens)
- if err != nil {
- return nil, nil, err
- }
- if _, ok := finalNamedVolumes[volume.Dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest)
- }
- finalNamedVolumes[volume.Dest] = volume
- default:
- return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1])
+ // If requested, add container init binary
+ if s.Init {
+ initPath := s.InitPath
+ if initPath == "" && rtc != nil {
+ initPath = rtc.Engine.InitPath
}
+ initMount, err := addContainerInitBinary(s, initPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ if _, ok := unifiedMounts[initMount.Destination]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination)
+ }
+ unifiedMounts[initMount.Destination] = initMount
}
- return finalMounts, finalNamedVolumes, nil
-}
-
-// Parse a single bind mount entry from the --mount flag.
-func getBindMount(args []string) (spec.Mount, error) { //nolint
- newMount := spec.Mount{
- Type: TypeBind,
+ // Before superseding, we need to find volume mounts which conflict with
+ // named volumes, and vice versa.
+ // We'll delete the conflicts here as we supersede.
+ for dest := range unifiedMounts {
+ if _, ok := baseVolumes[dest]; ok {
+ delete(baseVolumes, dest)
+ }
}
-
- var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
-
- for _, val := range args {
- kv := strings.Split(val, "=")
- switch kv[0] {
- case "bind-nonrecursive":
- newMount.Options = append(newMount.Options, "bind")
- case "ro", "rw":
- if setRORW {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once")
- }
- setRORW = true
- // Can be formatted as one of:
- // ro
- // ro=[true|false]
- // rw
- // rw=[true|false]
- switch len(kv) {
- case 1:
- newMount.Options = append(newMount.Options, kv[0])
- case 2:
- switch strings.ToLower(kv[1]) {
- case "true":
- newMount.Options = append(newMount.Options, kv[0])
- case "false":
- // Set the opposite only for rw
- // ro's opposite is the default
- if kv[0] == "rw" {
- newMount.Options = append(newMount.Options, "ro")
- }
- default:
- return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1])
- }
- default:
- return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val)
- }
- case "nosuid", "suid":
- if setSuid {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
- }
- setSuid = true
- newMount.Options = append(newMount.Options, kv[0])
- case "nodev", "dev":
- if setDev {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
- }
- setDev = true
- newMount.Options = append(newMount.Options, kv[0])
- case "noexec", "exec":
- if setExec {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
- }
- setExec = true
- newMount.Options = append(newMount.Options, kv[0])
- case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z":
- newMount.Options = append(newMount.Options, kv[0])
- case "bind-propagation":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- newMount.Options = append(newMount.Options, kv[1])
- case "src", "source":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- if err := parse.ValidateVolumeHostDir(kv[1]); err != nil {
- return newMount, err
- }
- newMount.Source = kv[1]
- setSource = true
- case "target", "dst", "destination":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
- return newMount, err
- }
- newMount.Destination = filepath.Clean(kv[1])
- setDest = true
- case "relabel":
- if setRelabel {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once")
- }
- setRelabel = true
- if len(kv) != 2 {
- return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
- }
- switch kv[1] {
- case "private":
- newMount.Options = append(newMount.Options, "z")
- case "shared":
- newMount.Options = append(newMount.Options, "Z")
- default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0])
- }
- default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ for dest := range unifiedVolumes {
+ if _, ok := baseMounts[dest]; ok {
+ delete(baseMounts, dest)
}
}
- if !setDest {
- return newMount, noDestError
+ // Supersede volumes-from/image volumes with unified volumes from above.
+ // This is an unconditional replacement.
+ for dest, mount := range unifiedMounts {
+ baseMounts[dest] = mount
}
-
- if !setSource {
- newMount.Source = newMount.Destination
+ for dest, volume := range unifiedVolumes {
+ baseVolumes[dest] = volume
}
- options, err := parse.ValidateVolumeOpts(newMount.Options)
- if err != nil {
- return newMount, err
- }
- newMount.Options = options
- return newMount, nil
-}
+ // TODO: Investigate moving readonlyTmpfs into here. Would be more
+ // correct.
-// Parse a single tmpfs mount entry from the --mount flag
-func getTmpfsMount(args []string) (spec.Mount, error) { //nolint
- newMount := spec.Mount{
- Type: TypeTmpfs,
- Source: TypeTmpfs,
+ // Check for conflicts between named volumes and mounts
+ for dest := range baseMounts {
+ if _, ok := baseVolumes[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
}
-
- var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
-
- for _, val := range args {
- kv := strings.Split(val, "=")
- switch kv[0] {
- case "tmpcopyup", "notmpcopyup":
- if setTmpcopyup {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once")
- }
- setTmpcopyup = true
- newMount.Options = append(newMount.Options, kv[0])
- case "ro", "rw":
- if setRORW {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
- }
- setRORW = true
- newMount.Options = append(newMount.Options, kv[0])
- case "nosuid", "suid":
- if setSuid {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
- }
- setSuid = true
- newMount.Options = append(newMount.Options, kv[0])
- case "nodev", "dev":
- if setDev {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
- }
- setDev = true
- newMount.Options = append(newMount.Options, kv[0])
- case "noexec", "exec":
- if setExec {
- return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
- }
- setExec = true
- newMount.Options = append(newMount.Options, kv[0])
- case "tmpfs-mode":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1]))
- case "tmpfs-size":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1]))
- case "src", "source":
- return newMount, errors.Errorf("source is not supported with tmpfs mounts")
- case "target", "dst", "destination":
- if len(kv) == 1 {
- return newMount, errors.Wrapf(optionArgError, kv[0])
- }
- if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
- return newMount, err
+ for dest := range baseVolumes {
+ if _, ok := baseMounts[dest]; ok {
+ return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest)
+ }
+ }
+ // Final step: maps to arrays
+ finalMounts := make([]spec.Mount, 0, len(baseMounts))
+ for _, mount := range baseMounts {
+ if mount.Type == TypeBind {
+ absSrc, err := filepath.Abs(mount.Source)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source)
}
- newMount.Destination = filepath.Clean(kv[1])
- setDest = true
- default:
- return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0])
+ mount.Source = absSrc
}
+ finalMounts = append(finalMounts, mount)
}
-
- if !setDest {
- return newMount, noDestError
+ finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes))
+ for _, volume := range baseVolumes {
+ finalVolumes = append(finalVolumes, volume)
}
- return newMount, nil
+ return finalMounts, finalVolumes, nil
}
-// Parse a single volume mount entry from the --mount flag.
-// Note that the volume-label option for named volumes is currently NOT supported.
-// TODO: add support for --volume-label
-func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint
- newVolume := new(libpod.ContainerNamedVolume)
+// Get image volumes from the given image
+func getImageVolumes(ctx context.Context, img *image.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ mounts := make(map[string]spec.Mount)
+ volumes := make(map[string]*specgen.NamedVolume)
- var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+ mode := strings.ToLower(s.ImageVolumeMode)
- for _, val := range args {
- kv := strings.Split(val, "=")
- switch kv[0] {
- case "ro", "rw":
- if setRORW {
- return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once")
- }
- setRORW = true
- newVolume.Options = append(newVolume.Options, kv[0])
- case "nosuid", "suid":
- if setSuid {
- return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once")
- }
- setSuid = true
- newVolume.Options = append(newVolume.Options, kv[0])
- case "nodev", "dev":
- if setDev {
- return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once")
- }
- setDev = true
- newVolume.Options = append(newVolume.Options, kv[0])
- case "noexec", "exec":
- if setExec {
- return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once")
- }
- setExec = true
- newVolume.Options = append(newVolume.Options, kv[0])
- case "volume-label":
- return nil, errors.Errorf("the --volume-label option is not presently implemented")
- case "src", "source":
- if len(kv) == 1 {
- return nil, errors.Wrapf(optionArgError, kv[0])
- }
- newVolume.Name = kv[1]
- setSource = true
- case "target", "dst", "destination":
- if len(kv) == 1 {
- return nil, errors.Wrapf(optionArgError, kv[0])
- }
- if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
- return nil, err
- }
- newVolume.Dest = filepath.Clean(kv[1])
- setDest = true
- default:
- return nil, errors.Wrapf(util.ErrBadMntOption, kv[0])
- }
+ // Image may be nil (rootfs in use), or image volume mode may be ignore.
+ if img == nil || mode == "ignore" {
+ return mounts, volumes, nil
}
- if !setSource {
- return nil, errors.Errorf("must set source volume")
+ inspect, err := img.InspectNoSize(ctx)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error inspecting image to get image volumes")
}
- if !setDest {
- return nil, noDestError
+ for volume := range inspect.Config.Volumes {
+ logrus.Debugf("Image has volume at %q", volume)
+ cleanDest := filepath.Clean(volume)
+ switch mode {
+ case "", "anonymous":
+ // Anonymous volumes have no name.
+ newVol := new(specgen.NamedVolume)
+ newVol.Dest = cleanDest
+ newVol.Options = []string{"rprivate", "rw", "nodev", "exec"}
+ volumes[cleanDest] = newVol
+ logrus.Debugf("Adding anonymous image volume at %q", cleanDest)
+ case "tmpfs":
+ mount := spec.Mount{
+ Destination: cleanDest,
+ Source: TypeTmpfs,
+ Type: TypeTmpfs,
+ Options: []string{"rprivate", "rw", "nodev", "exec"},
+ }
+ mounts[cleanDest] = mount
+ logrus.Debugf("Adding tmpfs image volume at %q", cleanDest)
+ }
}
- return newVolume, nil
+ return mounts, volumes, nil
}
-func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
- mounts := make(map[string]spec.Mount)
- volumes := make(map[string]*libpod.ContainerNamedVolume)
-
- volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]")
+func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
+ finalMounts := make(map[string]spec.Mount)
+ finalNamedVolumes := make(map[string]*specgen.NamedVolume)
- for _, vol := range vols {
- var (
- options []string
- src string
- dest string
- err error
- )
+ for _, volume := range volumesFrom {
+ var options []string
- splitVol := strings.Split(vol, ":")
- if len(splitVol) > 3 {
- return nil, nil, errors.Wrapf(volumeFormatErr, vol)
+ splitVol := strings.SplitN(volume, ":", 2)
+ if len(splitVol) == 2 {
+ splitOpts := strings.Split(splitVol[1], ",")
+ for _, opt := range splitOpts {
+ setRORW := false
+ setZ := false
+ switch opt {
+ case "z":
+ if setZ {
+ return nil, nil, errors.Errorf("cannot set :z more than once in mount options")
+ }
+ setZ = true
+ case "ro", "rw":
+ if setRORW {
+ return nil, nil, errors.Errorf("cannot set ro or rw options more than once")
+ }
+ setRORW = true
+ default:
+ return nil, nil, errors.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt)
+ }
+ }
+ options = splitOpts
}
- src = splitVol[0]
- if len(splitVol) == 1 {
- // This is an anonymous named volume. Only thing given
- // is destination.
- // Name/source will be blank, and populated by libpod.
- src = ""
- dest = splitVol[0]
- } else if len(splitVol) > 1 {
- dest = splitVol[1]
- }
- if len(splitVol) > 2 {
- if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil {
- return nil, nil, err
- }
+ ctr, err := runtime.LookupContainer(splitVol[0])
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0])
}
- // Do not check source dir for anonymous volumes
- if len(splitVol) > 1 {
- if err := parse.ValidateVolumeHostDir(src); err != nil {
- return nil, nil, err
- }
+ logrus.Debugf("Adding volumes from container %s", ctr.ID())
+
+ // Look up the container's user volumes. This gets us the
+ // destinations of all mounts the user added to the container.
+ userVolumesArr := ctr.UserVolumes()
+
+ // We're going to need to access them a lot, so convert to a map
+ // to reduce looping.
+ // We'll also use the map to indicate if we missed any volumes along the way.
+ userVolumes := make(map[string]bool)
+ for _, dest := range userVolumesArr {
+ userVolumes[dest] = false
}
- if err := parse.ValidateVolumeCtrDir(dest); err != nil {
- return nil, nil, err
+
+ // Now we get the container's spec and loop through its volumes
+ // and append them in if we can find them.
+ spec := ctr.Spec()
+ if spec == nil {
+ return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID())
}
+ for _, mnt := range spec.Mounts {
+ if mnt.Type != TypeBind {
+ continue
+ }
+ if _, exists := userVolumes[mnt.Destination]; exists {
+ userVolumes[mnt.Destination] = true
- cleanDest := filepath.Clean(dest)
+ if len(options) != 0 {
+ mnt.Options = options
+ }
- if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") {
- // This is not a named volume
- newMount := spec.Mount{
- Destination: cleanDest,
- Type: string(TypeBind),
- Source: src,
- Options: options,
- }
- if _, ok := mounts[newMount.Destination]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination)
- }
- mounts[newMount.Destination] = newMount
- } else {
- // This is a named volume
- newNamedVol := new(libpod.ContainerNamedVolume)
- newNamedVol.Name = src
- newNamedVol.Dest = cleanDest
- newNamedVol.Options = options
-
- if _, ok := volumes[newNamedVol.Dest]; ok {
- return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest)
+ if _, ok := finalMounts[mnt.Destination]; ok {
+ logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
+ }
+ finalMounts[mnt.Destination] = mnt
}
- volumes[newNamedVol.Dest] = newNamedVol
}
- logrus.Debugf("User mount %s:%s options %v", src, dest, options)
- }
+ // We're done with the spec mounts. Add named volumes.
+ // Add these unconditionally - none of them are automatically
+ // part of the container, as some spec mounts are.
+ namedVolumes := ctr.NamedVolumes()
+ for _, namedVol := range namedVolumes {
+ if _, exists := userVolumes[namedVol.Dest]; exists {
+ userVolumes[namedVol.Dest] = true
+ }
- return mounts, volumes, nil
-}
+ if len(options) != 0 {
+ namedVol.Options = options
+ }
-// Get mounts for container's image volumes
-// TODO deferred baude
-func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
- //mounts := make(map[string]spec.Mount)
- //volumes := make(map[string]*define.ContainerNamedVolume)
- //
- //if config.ImageVolumeType == "ignore" {
- // return mounts, volumes, nil
- //}
- //
- //for vol := range config.BuiltinImgVolumes {
- // cleanDest := filepath.Clean(vol)
- // logrus.Debugf("Adding image volume at %s", cleanDest)
- // if config.ImageVolumeType == "tmpfs" {
- // // Tmpfs image volumes are handled as mounts
- // mount := spec.Mount{
- // Destination: cleanDest,
- // Source: TypeTmpfs,
- // Type: TypeTmpfs,
- // Options: []string{"rprivate", "rw", "nodev", "exec"},
- // }
- // mounts[cleanDest] = mount
- // } else {
- // // Anonymous volumes have no name.
- // namedVolume := new(define.ContainerNamedVolume)
- // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"}
- // namedVolume.Dest = cleanDest
- // volumes[cleanDest] = namedVolume
- // }
- //}
- //
- //return mounts, volumes, nil
- return nil, nil, nil
-}
+ if _, ok := finalMounts[namedVol.Dest]; ok {
+ logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
+ }
-// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
-func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint
- m := make(map[string]spec.Mount)
- for _, i := range mounts {
- // Default options if nothing passed
- var options []string
- spliti := strings.Split(i, ":")
- destPath := spliti[0]
- if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
- return nil, err
- }
- if len(spliti) > 1 {
- options = strings.Split(spliti[1], ",")
- }
+ newVol := new(specgen.NamedVolume)
+ newVol.Dest = namedVol.Dest
+ newVol.Options = namedVol.Options
+ newVol.Name = namedVol.Name
- if _, ok := m[destPath]; ok {
- return nil, errors.Wrapf(errDuplicateDest, destPath)
+ finalNamedVolumes[namedVol.Dest] = newVol
}
- mount := spec.Mount{
- Destination: filepath.Clean(destPath),
- Type: string(TypeTmpfs),
- Options: options,
- Source: string(TypeTmpfs),
+ // Check if we missed any volumes
+ for volDest, found := range userVolumes {
+ if !found {
+ logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
+ }
}
- m[destPath] = mount
}
- return m, nil
+
+ return finalMounts, finalNamedVolumes, nil
}
// AddContainerInitBinary adds the init binary specified by path iff the
// container will run in a private PID namespace that is not shared with the
// host or another pre-existing container, where an init-like process is
// already running.
-//
-// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command
-// to execute the bind-mounted binary as PID 1.
-// TODO this needs to be worked on to work in new env
-func addContainerInitBinary(path string) (spec.Mount, error) { //nolint
+// This does *NOT* modify the container command - that must be done elsewhere.
+func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) {
mount := spec.Mount{
Destination: "/dev/init",
Type: TypeBind,
@@ -816,19 +308,18 @@ func addContainerInitBinary(path string) (spec.Mount, error) { //nolint
Options: []string{TypeBind, "ro"},
}
- //if path == "" {
- // return mount, fmt.Errorf("please specify a path to the container-init binary")
- //}
- //if !config.Pid.PidMode.IsPrivate() {
- // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
- //}
- //if config.Systemd {
- // return mount, fmt.Errorf("cannot use container-init binary with systemd")
- //}
- //if _, err := os.Stat(path); os.IsNotExist(err) {
- // return mount, errors.Wrap(err, "container-init binary not found on the host")
- //}
- //config.Command = append([]string{"/dev/init", "--"}, config.Command...)
+ if path == "" {
+ return mount, fmt.Errorf("please specify a path to the container-init binary")
+ }
+ if !s.PidNS.IsPrivate() {
+ return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)")
+ }
+ if s.Systemd == "true" || s.Systemd == "always" {
+ return mount, fmt.Errorf("cannot use container-init binary with systemd")
+ }
+ if _, err := os.Stat(path); os.IsNotExist(err) {
+ return mount, errors.Wrap(err, "container-init binary not found on the host")
+ }
return mount, nil
}
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index 2e7f80fe8..396563267 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -1,6 +1,8 @@
package specgen
import (
+ "strings"
+
"github.com/pkg/errors"
)
@@ -31,6 +33,11 @@ const (
// Slirp indicates that a slirp4netns network stack should
// be used
Slirp NamespaceMode = "slirp4netns"
+ // KeepId indicates a user namespace to keep the owner uid inside
+ // of the namespace itself
+ KeepID NamespaceMode = "keep-id"
+ // KeepId indicates to automatically create a user namespace
+ Auto NamespaceMode = "auto"
)
// Namespace describes the namespace
@@ -39,6 +46,12 @@ type Namespace struct {
Value string `json:"string,omitempty"`
}
+// IsDefault returns whether the namespace is set to the default setting (which
+// also includes the empty string).
+func (n *Namespace) IsDefault() bool {
+ return n.NSMode == Default || n.NSMode == ""
+}
+
// IsHost returns a bool if the namespace is host based
func (n *Namespace) IsHost() bool {
return n.NSMode == Host
@@ -64,16 +77,50 @@ func (n *Namespace) IsPrivate() bool {
return n.NSMode == Private
}
+// IsAuto indicates the namespace is auto
+func (n *Namespace) IsAuto() bool {
+ return n.NSMode == Auto
+}
+
+// IsKeepID indicates the namespace is KeepID
+func (n *Namespace) IsKeepID() bool {
+ return n.NSMode == KeepID
+}
+
+func validateUserNS(n *Namespace) error {
+ if n == nil {
+ return nil
+ }
+ switch n.NSMode {
+ case Auto, KeepID:
+ return nil
+ }
+ return n.validate()
+}
+
func validateNetNS(n *Namespace) error {
if n == nil {
return nil
}
switch n.NSMode {
- case Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp:
+ case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp:
break
default:
return errors.Errorf("invalid network %q", n.NSMode)
}
+
+ // Path and From Container MUST have a string value set
+ if n.NSMode == Path || n.NSMode == FromContainer {
+ if len(n.Value) < 1 {
+ return errors.Errorf("namespace mode %s requires a value", n.NSMode)
+ }
+ } else {
+ // All others must NOT set a string value
+ if len(n.Value) > 0 {
+ return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
+ }
+ }
+
return nil
}
@@ -83,6 +130,15 @@ func (n *Namespace) validate() error {
if n == nil {
return nil
}
+ switch n.NSMode {
+ case "", Default, Host, Path, FromContainer, FromPod, Private:
+ // Valid, do nothing
+ case NoNetwork, Bridge, Slirp:
+ return errors.Errorf("cannot use network modes with non-network namespace")
+ default:
+ return errors.Errorf("invalid namespace type %s specified", n.NSMode)
+ }
+
// Path and From Container MUST have a string value set
if n.NSMode == Path || n.NSMode == FromContainer {
if len(n.Value) < 1 {
@@ -96,3 +152,102 @@ func (n *Namespace) validate() error {
}
return nil
}
+
+// ParseNamespace parses a namespace in string form.
+// This is not intended for the network namespace, which has a separate
+// function.
+func ParseNamespace(ns string) (Namespace, error) {
+ toReturn := Namespace{}
+ switch {
+ case ns == "pod":
+ toReturn.NSMode = FromPod
+ case ns == "host":
+ toReturn.NSMode = Host
+ case ns == "private":
+ toReturn.NSMode = Private
+ case strings.HasPrefix(ns, "ns:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, errors.Errorf("must provide a path to a namespace when specifying ns:")
+ }
+ toReturn.NSMode = Path
+ toReturn.Value = split[1]
+ case strings.HasPrefix(ns, "container:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, errors.Errorf("must provide name or ID or a container when specifying container:")
+ }
+ toReturn.NSMode = FromContainer
+ toReturn.Value = split[1]
+ default:
+ return toReturn, errors.Errorf("unrecognized namespace mode %s passed", ns)
+ }
+
+ return toReturn, nil
+}
+
+// ParseUserNamespace parses a user namespace specification in string
+// form.
+func ParseUserNamespace(ns string) (Namespace, error) {
+ toReturn := Namespace{}
+ switch {
+ case ns == "auto":
+ toReturn.NSMode = Auto
+ return toReturn, nil
+ case strings.HasPrefix(ns, "auto:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, errors.Errorf("invalid setting for auto: mode")
+ }
+ toReturn.NSMode = Auto
+ toReturn.Value = split[1]
+ return toReturn, nil
+ case ns == "keep-id":
+ toReturn.NSMode = KeepID
+ return toReturn, nil
+ }
+ return ParseNamespace(ns)
+}
+
+// ParseNetworkNamespace parses a network namespace specification in string
+// form.
+// Returns a namespace and (optionally) a list of CNI networks to join.
+func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
+ toReturn := Namespace{}
+ var cniNetworks []string
+ switch {
+ case ns == "slirp4netns":
+ toReturn.NSMode = Slirp
+ case ns == "pod":
+ toReturn.NSMode = FromPod
+ case ns == "bridge":
+ toReturn.NSMode = Bridge
+ case ns == "none":
+ toReturn.NSMode = NoNetwork
+ case ns == "host":
+ toReturn.NSMode = Host
+ case ns == "private":
+ toReturn.NSMode = Private
+ case strings.HasPrefix(ns, "ns:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, errors.Errorf("must provide a path to a namespace when specifying ns:")
+ }
+ toReturn.NSMode = Path
+ toReturn.Value = split[1]
+ case strings.HasPrefix(ns, "container:"):
+ split := strings.SplitN(ns, ":", 2)
+ if len(split) != 2 {
+ return toReturn, nil, errors.Errorf("must provide name or ID or a container when specifying container:")
+ }
+ toReturn.NSMode = FromContainer
+ toReturn.Value = split[1]
+ default:
+ // Assume we have been given a list of CNI networks.
+ // Which only works in bridge mode, so set that.
+ cniNetworks = strings.Split(ns, ",")
+ toReturn.NSMode = Bridge
+ }
+
+ return toReturn, cniNetworks, nil
+}
diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go
index 9e9659fa9..98d59549e 100644
--- a/pkg/specgen/pod_validate.go
+++ b/pkg/specgen/pod_validate.go
@@ -1,14 +1,16 @@
package specgen
import (
- "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/util"
"github.com/pkg/errors"
)
var (
// ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid
ErrInvalidPodSpecConfig error = errors.New("invalid pod spec")
+ // containerConfig has the default configurations defined in containers.conf
+ containerConfig = util.DefaultContainerConfig()
)
func exclusivePodOptions(opt1, opt2 string) error {
@@ -60,7 +62,7 @@ func (p *PodSpecGenerator) Validate() error {
return exclusivePodOptions("NoInfra", "NoManageResolvConf")
}
}
- if p.NetNS.NSMode != Bridge {
+ if p.NetNS.NSMode != "" && p.NetNS.NSMode != Bridge && p.NetNS.NSMode != Default {
if len(p.PortMappings) > 0 {
return errors.New("PortMappings can only be used with Bridge mode networking")
}
@@ -96,10 +98,10 @@ func (p *PodSpecGenerator) Validate() error {
}
}
if len(p.InfraImage) < 1 {
- p.InfraImage = define.DefaultInfraImage
+ p.InfraImage = containerConfig.Engine.InfraImage
}
if len(p.InfraCommand) < 1 {
- p.InfraCommand = []string{define.DefaultInfraCommand}
+ p.InfraCommand = []string{containerConfig.Engine.InfraCommand}
}
return nil
}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 1a05733f9..20c8f8800 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -5,7 +5,6 @@ import (
"syscall"
"github.com/containers/image/v5/manifest"
- "github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -155,14 +154,23 @@ type ContainerStorageConfig struct {
// ImageVolumeMode indicates how image volumes will be created.
// Supported modes are "ignore" (do not create), "tmpfs" (create as
// tmpfs), and "anonymous" (create as anonymous volumes).
- // The default is anonymous.
+ // The default if unset is anonymous.
// Optional.
ImageVolumeMode string `json:"image_volume_mode,omitempty"`
- // VolumesFrom is a list of containers whose volumes will be added to
- // this container. Supported mount options may be added after the
- // container name with a : and include "ro" and "rw".
- // Optional.
+ // VolumesFrom is a set of containers whose volumes will be added to
+ // this container. The name or ID of the container must be provided, and
+ // may optionally be followed by a : and then one or more
+ // comma-separated options. Valid options are 'ro', 'rw', and 'z'.
+ // Options will be used for all volumes sourced from the container.
VolumesFrom []string `json:"volumes_from,omitempty"`
+ // Init specifies that an init binary will be mounted into the
+ // container, and will be used as PID1.
+ Init bool `json:"init,omitempty"`
+ // InitPath specifies the path to the init binary that will be added if
+ // Init is specified above. If not specified, the default set in the
+ // Libpod config will be used. Ignored if Init above is not set.
+ // Optional.
+ InitPath string `json:"init_path,omitempty"`
// Mounts are mounts that will be added to the container.
// These will supersede Image Volumes and VolumesFrom volumes where
// there are conflicts.
@@ -172,7 +180,7 @@ type ContainerStorageConfig struct {
// These will supersede Image Volumes and VolumesFrom volumes where
// there are conflicts.
// Optional.
- Volumes []*Volumes `json:"volumes,omitempty"`
+ Volumes []*NamedVolume `json:"volumes,omitempty"`
// Devices are devices that will be added to the container.
// Optional.
Devices []spec.LinuxDevice `json:"devices,omitempty"`
@@ -283,25 +291,20 @@ type ContainerNetworkConfig struct {
// namespace.
// Mandatory.
NetNS Namespace `json:"netns,omitempty"`
- // ConfigureNetNS is whether Libpod will configure the container's
- // network namespace to send and receive traffic.
- // Only available is NetNS is private - conflicts with other NetNS
- // modes.
- ConfigureNetNS bool `json:"configure_netns,omitempty"`
// StaticIP is the a IPv4 address of the container.
- // Only available if ConfigureNetNS is true.
+ // Only available if NetNS is set to Bridge.
// Optional.
StaticIP *net.IP `json:"static_ip,omitempty"`
// StaticIPv6 is a static IPv6 address to set in the container.
- // Only available if ConfigureNetNS is true.
+ // Only available if NetNS is set to Bridge.
// Optional.
StaticIPv6 *net.IP `json:"static_ipv6,omitempty"`
// StaticMAC is a static MAC address to set in the container.
- // Only available if ConfigureNetNS is true.
+ // Only available if NetNS is set to bridge.
// Optional.
StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"`
// PortBindings is a set of ports to map into the container.
- // Only available if ConfigureNetNS is true.
+ // Only available if NetNS is set to bridge or slirp.
// Optional.
PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"`
// PublishImagePorts will publish ports specified in the image to random
@@ -312,31 +315,31 @@ type ContainerNetworkConfig struct {
// If this list is empty, the default CNI network will be joined
// instead. If at least one entry is present, we will not join the
// default network (unless it is part of this list).
- // Only available if ConfigureNetNS is true.
+ // Only available if NetNS is set to bridge.
// Optional.
CNINetworks []string `json:"cni_networks,omitempty"`
// UseImageResolvConf indicates that resolv.conf should not be managed
// by Podman, but instead sourced from the image.
// Conflicts with DNSServer, DNSSearch, DNSOption.
UseImageResolvConf bool `json:"use_image_resolve_conf,omitempty"`
- // DNSServer is a set of DNS servers that will be used in the
+ // DNSServers is a set of DNS servers that will be used in the
// container's resolv.conf, replacing the host's DNS Servers which are
// used by default.
// Conflicts with UseImageResolvConf.
// Optional.
- DNSServer []net.IP `json:"dns_server,omitempty"`
+ DNSServers []net.IP `json:"dns_server,omitempty"`
// DNSSearch is a set of DNS search domains that will be used in the
// container's resolv.conf, replacing the host's DNS search domains
// which are used by default.
// Conflicts with UseImageResolvConf.
// Optional.
DNSSearch []string `json:"dns_search,omitempty"`
- // DNSOption is a set of DNS options that will be used in the
+ // DNSOptions is a set of DNS options that will be used in the
// container's resolv.conf, replacing the host's DNS options which are
// used by default.
// Conflicts with UseImageResolvConf.
// Optional.
- DNSOption []string `json:"dns_option,omitempty"`
+ DNSOptions []string `json:"dns_option,omitempty"`
// UseImageHosts indicates that /etc/hosts should not be managed by
// Podman, and instead sourced from the image.
// Conflicts with HostAdd.
@@ -393,27 +396,30 @@ type SpecGenerator struct {
ContainerHealthCheckConfig
}
-// Volumes is a temporary struct to hold input from the User
-type Volumes struct {
- Name string
- Dest string
+// NamedVolume holds information about a named volume that will be mounted into
+// the container.
+type NamedVolume struct {
+ // Name is the name of the named volume to be mounted. May be empty.
+ // If empty, a new named volume with a pseudorandomly generated name
+ // will be mounted at the given destination.
+ Name string
+ // Destination to mount the named volume within the container. Must be
+ // an absolute path. Path will be created if it does not exist.
+ Dest string
+ // Options are options that the named volume will be mounted with.
Options []string
}
// NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs
-func NewSpecGenerator(image string) *SpecGenerator {
- networkConfig := ContainerNetworkConfig{
- NetNS: Namespace{
- NSMode: Bridge,
- },
- }
- csc := ContainerStorageConfig{Image: image}
- if rootless.IsRootless() {
- networkConfig.NetNS.NSMode = Slirp
+func NewSpecGenerator(arg string, rootfs bool) *SpecGenerator {
+ csc := ContainerStorageConfig{}
+ if rootfs {
+ csc.Rootfs = arg
+ } else {
+ csc.Image = arg
}
return &SpecGenerator{
ContainerStorageConfig: csc,
- ContainerNetworkConfig: networkConfig,
}
}
diff --git a/pkg/sysinfo/README.md b/pkg/sysinfo/README.md
deleted file mode 100644
index c1530cef0..000000000
--- a/pkg/sysinfo/README.md
+++ /dev/null
@@ -1 +0,0 @@
-SysInfo stores information about which features a kernel supports.
diff --git a/pkg/sysinfo/numcpu.go b/pkg/sysinfo/numcpu.go
deleted file mode 100644
index aeb1a3a80..000000000
--- a/pkg/sysinfo/numcpu.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// +build !linux,!windows
-
-package sysinfo
-
-import (
- "runtime"
-)
-
-// NumCPU returns the number of CPUs
-func NumCPU() int {
- return runtime.NumCPU()
-}
diff --git a/pkg/sysinfo/numcpu_linux.go b/pkg/sysinfo/numcpu_linux.go
deleted file mode 100644
index f1d2d9db3..000000000
--- a/pkg/sysinfo/numcpu_linux.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// +build linux
-
-package sysinfo
-
-import (
- "runtime"
- "unsafe"
-
- "golang.org/x/sys/unix"
-)
-
-// numCPU queries the system for the count of threads available
-// for use to this process.
-//
-// Issues two syscalls.
-// Returns 0 on errors. Use |runtime.NumCPU| in that case.
-func numCPU() int {
- // Gets the affinity mask for a process: The very one invoking this function.
- pid, _, _ := unix.RawSyscall(unix.SYS_GETPID, 0, 0, 0)
-
- var mask [1024 / 64]uintptr
- _, _, err := unix.RawSyscall(unix.SYS_SCHED_GETAFFINITY, pid, uintptr(len(mask)*8), uintptr(unsafe.Pointer(&mask[0])))
- if err != 0 {
- return 0
- }
-
- // For every available thread a bit is set in the mask.
- ncpu := 0
- for _, e := range mask {
- if e == 0 {
- continue
- }
- ncpu += int(popcnt(uint64(e)))
- }
- return ncpu
-}
-
-// NumCPU returns the number of CPUs which are currently online
-func NumCPU() int {
- if ncpu := numCPU(); ncpu > 0 {
- return ncpu
- }
- return runtime.NumCPU()
-}
diff --git a/pkg/sysinfo/numcpu_windows.go b/pkg/sysinfo/numcpu_windows.go
deleted file mode 100644
index 1d89dd550..000000000
--- a/pkg/sysinfo/numcpu_windows.go
+++ /dev/null
@@ -1,37 +0,0 @@
-// +build windows
-
-package sysinfo
-
-import (
- "runtime"
- "unsafe"
-
- "golang.org/x/sys/windows"
-)
-
-var (
- kernel32 = windows.NewLazySystemDLL("kernel32.dll")
- getCurrentProcess = kernel32.NewProc("GetCurrentProcess")
- getProcessAffinityMask = kernel32.NewProc("GetProcessAffinityMask")
-)
-
-func numCPU() int {
- // Gets the affinity mask for a process
- var mask, sysmask uintptr
- currentProcess, _, _ := getCurrentProcess.Call()
- ret, _, _ := getProcessAffinityMask.Call(currentProcess, uintptr(unsafe.Pointer(&mask)), uintptr(unsafe.Pointer(&sysmask)))
- if ret == 0 {
- return 0
- }
- // For every available thread a bit is set in the mask.
- ncpu := int(popcnt(uint64(mask)))
- return ncpu
-}
-
-// NumCPU returns the number of CPUs which are currently online
-func NumCPU() int {
- if ncpu := numCPU(); ncpu > 0 {
- return ncpu
- }
- return runtime.NumCPU()
-}
diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go
deleted file mode 100644
index 686f66ce5..000000000
--- a/pkg/sysinfo/sysinfo.go
+++ /dev/null
@@ -1,153 +0,0 @@
-package sysinfo
-
-import "github.com/docker/docker/pkg/parsers"
-
-// SysInfo stores information about which features a kernel supports.
-// TODO Windows: Factor out platform specific capabilities.
-type SysInfo struct {
- // Whether the kernel supports AppArmor or not
- AppArmor bool
- // Whether the kernel supports Seccomp or not
- Seccomp bool
-
- cgroupMemInfo
- cgroupCPUInfo
- cgroupBlkioInfo
- cgroupCpusetInfo
- cgroupPids
-
- // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work
- IPv4ForwardingDisabled bool
-
- // Whether bridge-nf-call-iptables is supported or not
- BridgeNFCallIPTablesDisabled bool
-
- // Whether bridge-nf-call-ip6tables is supported or not
- BridgeNFCallIP6TablesDisabled bool
-
- // Whether the cgroup has the mountpoint of "devices" or not
- CgroupDevicesEnabled bool
-}
-
-type cgroupMemInfo struct {
- // Whether memory limit is supported or not
- MemoryLimit bool
-
- // Whether swap limit is supported or not
- SwapLimit bool
-
- // Whether soft limit is supported or not
- MemoryReservation bool
-
- // Whether OOM killer disable is supported or not
- OomKillDisable bool
-
- // Whether memory swappiness is supported or not
- MemorySwappiness bool
-
- // Whether kernel memory limit is supported or not
- KernelMemory bool
-}
-
-type cgroupCPUInfo struct {
- // Whether CPU shares is supported or not
- CPUShares bool
-
- // Whether CPU CFS(Completely Fair Scheduler) period is supported or not
- CPUCfsPeriod bool
-
- // Whether CPU CFS(Completely Fair Scheduler) quota is supported or not
- CPUCfsQuota bool
-
- // Whether CPU real-time period is supported or not
- CPURealtimePeriod bool
-
- // Whether CPU real-time runtime is supported or not
- CPURealtimeRuntime bool
-}
-
-type cgroupBlkioInfo struct {
- // Whether Block IO weight is supported or not
- BlkioWeight bool
-
- // Whether Block IO weight_device is supported or not
- BlkioWeightDevice bool
-
- // Whether Block IO read limit in bytes per second is supported or not
- BlkioReadBpsDevice bool
-
- // Whether Block IO write limit in bytes per second is supported or not
- BlkioWriteBpsDevice bool
-
- // Whether Block IO read limit in IO per second is supported or not
- BlkioReadIOpsDevice bool
-
- // Whether Block IO write limit in IO per second is supported or not
- BlkioWriteIOpsDevice bool
-}
-
-type cgroupCpusetInfo struct {
- // Whether Cpuset is supported or not
- Cpuset bool
-
- // Available Cpuset's cpus
- Cpus string
-
- // Available Cpuset's memory nodes
- Mems string
-}
-
-type cgroupPids struct {
- // Whether Pids Limit is supported or not
- PidsLimit bool
-}
-
-// IsCpusetCpusAvailable returns `true` if the provided string set is contained
-// in cgroup's cpuset.cpus set, `false` otherwise.
-// If error is not nil a parsing error occurred.
-func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) {
- return isCpusetListAvailable(provided, c.Cpus)
-}
-
-// IsCpusetMemsAvailable returns `true` if the provided string set is contained
-// in cgroup's cpuset.mems set, `false` otherwise.
-// If error is not nil a parsing error occurred.
-func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) {
- return isCpusetListAvailable(provided, c.Mems)
-}
-
-func isCpusetListAvailable(provided, available string) (bool, error) {
- parsedProvided, err := parsers.ParseUintList(provided)
- if err != nil {
- return false, err
- }
- parsedAvailable, err := parsers.ParseUintList(available)
- if err != nil {
- return false, err
- }
- for k := range parsedProvided {
- if !parsedAvailable[k] {
- return false, nil
- }
- }
- return true, nil
-}
-
-// Returns bit count of 1, used by NumCPU
-func popcnt(x uint64) (n byte) {
- x -= (x >> 1) & 0x5555555555555555
- x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
- x += x >> 4
- x &= 0x0f0f0f0f0f0f0f0f
- x *= 0x0101010101010101
- return byte(x >> 56)
-}
-
-// GetDefaultPidsLimit returns the default pids limit to run containers with
-func GetDefaultPidsLimit() int64 {
- sysInfo := New(true)
- if !sysInfo.PidsLimit {
- return 0
- }
- return 4096
-}
diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go
deleted file mode 100644
index 76bda23c6..000000000
--- a/pkg/sysinfo/sysinfo_linux.go
+++ /dev/null
@@ -1,261 +0,0 @@
-package sysinfo
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "strings"
-
- cg "github.com/containers/libpod/pkg/cgroups"
- "github.com/opencontainers/runc/libcontainer/cgroups"
- "github.com/sirupsen/logrus"
- "golang.org/x/sys/unix"
-)
-
-func findCgroupMountpoints() (map[string]string, error) {
- cgMounts, err := cgroups.GetCgroupMounts(false)
- if err != nil {
- return nil, fmt.Errorf("failed to parse cgroup information: %v", err)
- }
- mps := make(map[string]string)
- for _, m := range cgMounts {
- for _, ss := range m.Subsystems {
- mps[ss] = m.Mountpoint
- }
- }
- return mps, nil
-}
-
-// New returns a new SysInfo, using the filesystem to detect which features
-// the kernel supports. If `quiet` is `false` warnings are printed in logs
-// whenever an error occurs or misconfigurations are present.
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- cgMounts, err := findCgroupMountpoints()
- if err != nil {
- logrus.Warnf("Failed to parse cgroup information: %v", err)
- } else {
- sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet)
- sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet)
- sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet)
- sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet)
- sysInfo.cgroupPids = checkCgroupPids(quiet)
- }
-
- _, ok := cgMounts["devices"]
- sysInfo.CgroupDevicesEnabled = ok
-
- sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward")
- sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables")
- sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables")
-
- // Check if AppArmor is supported.
- if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
- sysInfo.AppArmor = true
- }
-
- // Check if Seccomp is supported, via CONFIG_SECCOMP.
- if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
- // Make sure the kernel has CONFIG_SECCOMP_FILTER.
- if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
- sysInfo.Seccomp = true
- }
- }
-
- return sysInfo
-}
-
-// checkCgroupMem reads the memory information from the memory cgroup mount point.
-func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo {
- mountPoint, ok := cgMounts["memory"]
- if !ok {
- if !quiet {
- logrus.Warn("Your kernel does not support cgroup memory limit")
- }
- return cgroupMemInfo{}
- }
-
- swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes")
- if !quiet && !swapLimit {
- logrus.Warn("Your kernel does not support swap memory limit")
- }
- memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes")
- if !quiet && !memoryReservation {
- logrus.Warn("Your kernel does not support memory reservation")
- }
- oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control")
- if !quiet && !oomKillDisable {
- logrus.Warn("Your kernel does not support oom control")
- }
- memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness")
- if !quiet && !memorySwappiness {
- logrus.Warn("Your kernel does not support memory swappiness")
- }
- kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes")
- if !quiet && !kernelMemory {
- logrus.Warn("Your kernel does not support kernel memory limit")
- }
-
- return cgroupMemInfo{
- MemoryLimit: true,
- SwapLimit: swapLimit,
- MemoryReservation: memoryReservation,
- OomKillDisable: oomKillDisable,
- MemorySwappiness: memorySwappiness,
- KernelMemory: kernelMemory,
- }
-}
-
-// checkCgroupCPU reads the cpu information from the cpu cgroup mount point.
-func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo {
- mountPoint, ok := cgMounts["cpu"]
- if !ok {
- if !quiet {
- logrus.Warn("Unable to find cpu cgroup in mounts")
- }
- return cgroupCPUInfo{}
- }
-
- cpuShares := cgroupEnabled(mountPoint, "cpu.shares")
- if !quiet && !cpuShares {
- logrus.Warn("Your kernel does not support cgroup cpu shares")
- }
-
- cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us")
- if !quiet && !cpuCfsPeriod {
- logrus.Warn("Your kernel does not support cgroup cfs period")
- }
-
- cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us")
- if !quiet && !cpuCfsQuota {
- logrus.Warn("Your kernel does not support cgroup cfs quotas")
- }
-
- cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us")
- if !quiet && !cpuRealtimePeriod {
- logrus.Warn("Your kernel does not support cgroup rt period")
- }
-
- cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us")
- if !quiet && !cpuRealtimeRuntime {
- logrus.Warn("Your kernel does not support cgroup rt runtime")
- }
-
- return cgroupCPUInfo{
- CPUShares: cpuShares,
- CPUCfsPeriod: cpuCfsPeriod,
- CPUCfsQuota: cpuCfsQuota,
- CPURealtimePeriod: cpuRealtimePeriod,
- CPURealtimeRuntime: cpuRealtimeRuntime,
- }
-}
-
-// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point.
-func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo {
- mountPoint, ok := cgMounts["blkio"]
- if !ok {
- if !quiet {
- logrus.Warn("Unable to find blkio cgroup in mounts")
- }
- return cgroupBlkioInfo{}
- }
-
- weight := cgroupEnabled(mountPoint, "blkio.weight")
- if !quiet && !weight {
- logrus.Warn("Your kernel does not support cgroup blkio weight")
- }
-
- weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device")
- if !quiet && !weightDevice {
- logrus.Warn("Your kernel does not support cgroup blkio weight_device")
- }
-
- readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
- if !quiet && !readBpsDevice {
- logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device")
- }
-
- writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
- if !quiet && !writeBpsDevice {
- logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device")
- }
- readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device")
- if !quiet && !readIOpsDevice {
- logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device")
- }
-
- writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device")
- if !quiet && !writeIOpsDevice {
- logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device")
- }
- return cgroupBlkioInfo{
- BlkioWeight: weight,
- BlkioWeightDevice: weightDevice,
- BlkioReadBpsDevice: readBpsDevice,
- BlkioWriteBpsDevice: writeBpsDevice,
- BlkioReadIOpsDevice: readIOpsDevice,
- BlkioWriteIOpsDevice: writeIOpsDevice,
- }
-}
-
-// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point.
-func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo {
- mountPoint, ok := cgMounts["cpuset"]
- if !ok {
- if !quiet {
- logrus.Warn("Unable to find cpuset cgroup in mounts")
- }
- return cgroupCpusetInfo{}
- }
-
- cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus"))
- if err != nil {
- return cgroupCpusetInfo{}
- }
-
- mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems"))
- if err != nil {
- return cgroupCpusetInfo{}
- }
-
- return cgroupCpusetInfo{
- Cpuset: true,
- Cpus: strings.TrimSpace(string(cpus)),
- Mems: strings.TrimSpace(string(mems)),
- }
-}
-
-// checkCgroupPids reads the pids information from the pids cgroup mount point.
-func checkCgroupPids(quiet bool) cgroupPids {
- cgroup2, err := cg.IsCgroup2UnifiedMode()
- if err != nil {
- logrus.Errorf("Failed to check cgroups version: %v", err)
- }
- if !cgroup2 {
- _, err := cgroups.FindCgroupMountpoint("", "pids")
- if err != nil {
- if !quiet {
- logrus.Warn(err)
- }
- return cgroupPids{}
- }
- }
-
- return cgroupPids{
- PidsLimit: true,
- }
-}
-
-func cgroupEnabled(mountPoint, name string) bool {
- _, err := os.Stat(path.Join(mountPoint, name))
- return err == nil
-}
-
-func readProcBool(path string) bool {
- val, err := ioutil.ReadFile(path)
- if err != nil {
- return false
- }
- return strings.TrimSpace(string(val)) == "1"
-}
diff --git a/pkg/sysinfo/sysinfo_linux_test.go b/pkg/sysinfo/sysinfo_linux_test.go
deleted file mode 100644
index 860784f2a..000000000
--- a/pkg/sysinfo/sysinfo_linux_test.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package sysinfo
-
-import (
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "testing"
-
- "github.com/stretchr/testify/require"
- "golang.org/x/sys/unix"
-)
-
-func TestReadProcBool(t *testing.T) {
- tmpDir, err := ioutil.TempDir("", "test-sysinfo-proc")
- require.NoError(t, err)
- defer os.RemoveAll(tmpDir)
-
- procFile := filepath.Join(tmpDir, "read-proc-bool")
- err = ioutil.WriteFile(procFile, []byte("1"), 0644)
- require.NoError(t, err)
-
- if !readProcBool(procFile) {
- t.Fatal("expected proc bool to be true, got false")
- }
-
- if err := ioutil.WriteFile(procFile, []byte("0"), 0644); err != nil {
- t.Fatal(err)
- }
- if readProcBool(procFile) {
- t.Fatal("expected proc bool to be false, got true")
- }
-
- if readProcBool(path.Join(tmpDir, "no-exist")) {
- t.Fatal("should be false for non-existent entry")
- }
-
-}
-
-func TestCgroupEnabled(t *testing.T) {
- cgroupDir, err := ioutil.TempDir("", "cgroup-test")
- require.NoError(t, err)
- defer os.RemoveAll(cgroupDir)
-
- if cgroupEnabled(cgroupDir, "test") {
- t.Fatal("cgroupEnabled should be false")
- }
-
- err = ioutil.WriteFile(path.Join(cgroupDir, "test"), []byte{}, 0644)
- require.NoError(t, err)
-
- if !cgroupEnabled(cgroupDir, "test") {
- t.Fatal("cgroupEnabled should be true")
- }
-}
-
-func TestNew(t *testing.T) {
- sysInfo := New(false)
- require.NotNil(t, sysInfo)
- checkSysInfo(t, sysInfo)
-
- sysInfo = New(true)
- require.NotNil(t, sysInfo)
- checkSysInfo(t, sysInfo)
-}
-
-func checkSysInfo(t *testing.T, sysInfo *SysInfo) {
- // Check if Seccomp is supported, via CONFIG_SECCOMP.then sysInfo.Seccomp must be TRUE , else FALSE
- if err := unix.Prctl(unix.PR_GET_SECCOMP, 0, 0, 0, 0); err != unix.EINVAL {
- // Make sure the kernel has CONFIG_SECCOMP_FILTER.
- if err := unix.Prctl(unix.PR_SET_SECCOMP, unix.SECCOMP_MODE_FILTER, 0, 0, 0); err != unix.EINVAL {
- require.True(t, sysInfo.Seccomp)
- }
- } else {
- require.False(t, sysInfo.Seccomp)
- }
-}
-
-func TestNewAppArmorEnabled(t *testing.T) {
- // Check if AppArmor is supported. then it must be TRUE , else FALSE
- if _, err := os.Stat("/sys/kernel/security/apparmor"); err != nil {
- t.Skip("App Armor Must be Enabled")
- }
-
- sysInfo := New(true)
- require.True(t, sysInfo.AppArmor)
-}
-
-func TestNewAppArmorDisabled(t *testing.T) {
- // Check if AppArmor is supported. then it must be TRUE , else FALSE
- if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) {
- t.Skip("App Armor Must be Disabled")
- }
-
- sysInfo := New(true)
- require.False(t, sysInfo.AppArmor)
-}
-
-func TestNumCPU(t *testing.T) {
- cpuNumbers := NumCPU()
- if cpuNumbers <= 0 {
- t.Fatal("CPU returned must be greater than zero")
- }
-}
diff --git a/pkg/sysinfo/sysinfo_solaris.go b/pkg/sysinfo/sysinfo_solaris.go
deleted file mode 100644
index 7463cdd8f..000000000
--- a/pkg/sysinfo/sysinfo_solaris.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// +build solaris,cgo
-
-package sysinfo
-
-import (
- "bytes"
- "os/exec"
- "strconv"
- "strings"
-)
-
-/*
-#cgo LDFLAGS: -llgrp
-#cgo CFLAGS: -Wall -Werror
-#include <unistd.h>
-#include <stdlib.h>
-#include <sys/lgrp_user.h>
-int getLgrpCount() {
- lgrp_cookie_t lgrpcookie = LGRP_COOKIE_NONE;
- uint_t nlgrps;
-
- if ((lgrpcookie = lgrp_init(LGRP_VIEW_OS)) == LGRP_COOKIE_NONE) {
- return -1;
- }
- nlgrps = lgrp_nlgrps(lgrpcookie);
- return nlgrps;
-}
-*/
-import "C"
-
-// IsCPUSharesAvailable returns whether CPUShares setting is supported.
-// We need FSS to be set as default scheduling class to support CPU Shares
-func IsCPUSharesAvailable() bool {
- cmd := exec.Command("/usr/sbin/dispadmin", "-d")
- outBuf := new(bytes.Buffer)
- errBuf := new(bytes.Buffer)
- cmd.Stderr = errBuf
- cmd.Stdout = outBuf
-
- if err := cmd.Run(); err != nil {
- return false
- }
- return (strings.Contains(outBuf.String(), "FSS"))
-}
-
-// New returns a new SysInfo, using the filesystem to detect which features
-// the kernel supports.
-//NOTE Solaris: If we change the below capabilities be sure
-// to update verifyPlatformContainerSettings() in daemon_solaris.go
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- sysInfo.cgroupMemInfo = setCgroupMem(quiet)
- sysInfo.cgroupCPUInfo = setCgroupCPU(quiet)
- sysInfo.cgroupBlkioInfo = setCgroupBlkioInfo(quiet)
- sysInfo.cgroupCpusetInfo = setCgroupCPUsetInfo(quiet)
-
- sysInfo.IPv4ForwardingDisabled = false
-
- sysInfo.AppArmor = false
-
- return sysInfo
-}
-
-// setCgroupMem reads the memory information for Solaris.
-func setCgroupMem(quiet bool) cgroupMemInfo {
-
- return cgroupMemInfo{
- MemoryLimit: true,
- SwapLimit: true,
- MemoryReservation: false,
- OomKillDisable: false,
- MemorySwappiness: false,
- KernelMemory: false,
- }
-}
-
-// setCgroupCPU reads the cpu information for Solaris.
-func setCgroupCPU(quiet bool) cgroupCPUInfo {
-
- return cgroupCPUInfo{
- CPUShares: true,
- CPUCfsPeriod: false,
- CPUCfsQuota: true,
- CPURealtimePeriod: false,
- CPURealtimeRuntime: false,
- }
-}
-
-// blkio switches are not supported in Solaris.
-func setCgroupBlkioInfo(quiet bool) cgroupBlkioInfo {
-
- return cgroupBlkioInfo{
- BlkioWeight: false,
- BlkioWeightDevice: false,
- }
-}
-
-// setCgroupCPUsetInfo reads the cpuset information for Solaris.
-func setCgroupCPUsetInfo(quiet bool) cgroupCpusetInfo {
-
- return cgroupCpusetInfo{
- Cpuset: true,
- Cpus: getCPUCount(),
- Mems: getLgrpCount(),
- }
-}
-
-func getCPUCount() string {
- ncpus := C.sysconf(C._SC_NPROCESSORS_ONLN)
- if ncpus <= 0 {
- return ""
- }
- return strconv.FormatInt(int64(ncpus), 16)
-}
-
-func getLgrpCount() string {
- nlgrps := C.getLgrpCount()
- if nlgrps <= 0 {
- return ""
- }
- return strconv.FormatInt(int64(nlgrps), 16)
-}
diff --git a/pkg/sysinfo/sysinfo_test.go b/pkg/sysinfo/sysinfo_test.go
deleted file mode 100644
index 895828f26..000000000
--- a/pkg/sysinfo/sysinfo_test.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package sysinfo
-
-import "testing"
-
-func TestIsCpusetListAvailable(t *testing.T) {
- cases := []struct {
- provided string
- available string
- res bool
- err bool
- }{
- {"1", "0-4", true, false},
- {"01,3", "0-4", true, false},
- {"", "0-7", true, false},
- {"1--42", "0-7", false, true},
- {"1-42", "00-1,8,,9", false, true},
- {"1,41-42", "43,45", false, false},
- {"0-3", "", false, false},
- }
- for _, c := range cases {
- r, err := isCpusetListAvailable(c.provided, c.available)
- if (c.err && err == nil) && r != c.res {
- t.Fatalf("Expected pair: %v, %v for %s, %s. Got %v, %v instead", c.res, c.err, c.provided, c.available, c.err && err == nil, r)
- }
- }
-}
diff --git a/pkg/sysinfo/sysinfo_unix.go b/pkg/sysinfo/sysinfo_unix.go
deleted file mode 100644
index 45f3ef1c6..000000000
--- a/pkg/sysinfo/sysinfo_unix.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build !linux,!solaris,!windows
-
-package sysinfo
-
-// New returns an empty SysInfo for non linux nor solaris for now.
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- return sysInfo
-}
diff --git a/pkg/sysinfo/sysinfo_windows.go b/pkg/sysinfo/sysinfo_windows.go
deleted file mode 100644
index 4e6255bc5..000000000
--- a/pkg/sysinfo/sysinfo_windows.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// +build windows
-
-package sysinfo
-
-// New returns an empty SysInfo for windows for now.
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- return sysInfo
-}
diff --git a/pkg/systemd/activation.go b/pkg/systemd/activation.go
index c8b2389dc..8f75f9cca 100644
--- a/pkg/systemd/activation.go
+++ b/pkg/systemd/activation.go
@@ -3,38 +3,33 @@ package systemd
import (
"os"
"strconv"
- "strings"
)
// SocketActivated determine if podman is running under the socket activation protocol
+// Criteria is based on the expectations of "github.com/coreos/go-systemd/v22/activation"
func SocketActivated() bool {
- pid, pid_found := os.LookupEnv("LISTEN_PID")
- fds, fds_found := os.LookupEnv("LISTEN_FDS")
- fdnames, fdnames_found := os.LookupEnv("LISTEN_FDNAMES")
-
- if !(pid_found && fds_found && fdnames_found) {
+ pid, found := os.LookupEnv("LISTEN_PID")
+ if !found {
return false
}
-
p, err := strconv.Atoi(pid)
if err != nil || p != os.Getpid() {
return false
}
+ fds, found := os.LookupEnv("LISTEN_FDS")
+ if !found {
+ return false
+ }
nfds, err := strconv.Atoi(fds)
- if err != nil || nfds < 1 {
+ if err != nil || nfds == 0 {
return false
}
- // First available file descriptor is always 3.
- if nfds > 1 {
- names := strings.Split(fdnames, ":")
- for _, n := range names {
- if strings.Contains(n, "podman") {
- return true
- }
- }
+ // "github.com/coreos/go-systemd/v22/activation" will use and validate this variable's
+ // value. We're just providing a fast fail
+ if _, found = os.LookupEnv("LISTEN_FDNAMES"); !found {
+ return false
}
-
return true
}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index babf7dfc9..917f57742 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -13,6 +13,7 @@ import (
"time"
"github.com/BurntSushi/toml"
+ "github.com/containers/common/pkg/config"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/pkg/errorhandling"
"github.com/containers/libpod/pkg/namespaces"
@@ -21,12 +22,22 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
- "github.com/opencontainers/selinux/go-selinux"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
)
+var containerConfig *config.Config
+
+func init() {
+ var err error
+ containerConfig, err = config.Default()
+ if err != nil {
+ logrus.Error(err)
+ os.Exit(1)
+ }
+}
+
// Helper function to determine the username/password passed
// in the creds string. It could be either or both.
func parseCreds(creds string) (string, string) {
@@ -319,6 +330,58 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) {
return sig, nil
}
+// GetKeepIDMapping returns the mappings and the user to use when keep-id is used
+func GetKeepIDMapping() (*storage.IDMappingOptions, int, int, error) {
+ options := storage.IDMappingOptions{
+ HostUIDMapping: true,
+ HostGIDMapping: true,
+ }
+ uid, gid := 0, 0
+ if rootless.IsRootless() {
+ min := func(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+ }
+
+ uid = rootless.GetRootlessUID()
+ gid = rootless.GetRootlessGID()
+
+ uids, gids, err := rootless.GetConfiguredMappings()
+ if err != nil {
+ return nil, -1, -1, errors.Wrapf(err, "cannot read mappings")
+ }
+ maxUID, maxGID := 0, 0
+ for _, u := range uids {
+ maxUID += u.Size
+ }
+ for _, g := range gids {
+ maxGID += g.Size
+ }
+
+ options.UIDMap, options.GIDMap = nil, nil
+
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
+ if maxUID > uid {
+ options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
+ }
+
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
+ if maxGID > gid {
+ options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
+ }
+
+ options.HostUIDMapping = false
+ options.HostGIDMapping = false
+
+ }
+ // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
+ return &options, uid, gid, nil
+}
+
// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []string, subUIDMap, subGIDMap string) (*storage.IDMappingOptions, error) {
options := storage.IDMappingOptions{
@@ -339,53 +402,8 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin
return &options, nil
}
if mode.IsKeepID() {
- if len(uidMapSlice) > 0 || len(gidMapSlice) > 0 {
- return nil, errors.New("cannot specify custom mappings with --userns=keep-id")
- }
- if len(subUIDMap) > 0 || len(subGIDMap) > 0 {
- return nil, errors.New("cannot specify subuidmap or subgidmap with --userns=keep-id")
- }
- if rootless.IsRootless() {
- min := func(a, b int) int {
- if a < b {
- return a
- }
- return b
- }
-
- uid := rootless.GetRootlessUID()
- gid := rootless.GetRootlessGID()
-
- uids, gids, err := rootless.GetConfiguredMappings()
- if err != nil {
- return nil, errors.Wrapf(err, "cannot read mappings")
- }
- maxUID, maxGID := 0, 0
- for _, u := range uids {
- maxUID += u.Size
- }
- for _, g := range gids {
- maxGID += g.Size
- }
-
- options.UIDMap, options.GIDMap = nil, nil
-
- options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(uid, maxUID)})
- options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid, HostID: 0, Size: 1})
- if maxUID > uid {
- options.UIDMap = append(options.UIDMap, idtools.IDMap{ContainerID: uid + 1, HostID: uid + 1, Size: maxUID - uid})
- }
-
- options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: 0, HostID: 1, Size: min(gid, maxGID)})
- options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid, HostID: 0, Size: 1})
- if maxGID > gid {
- options.GIDMap = append(options.GIDMap, idtools.IDMap{ContainerID: gid + 1, HostID: gid + 1, Size: maxGID - gid})
- }
-
- options.HostUIDMapping = false
- options.HostGIDMapping = false
- }
- // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op
+ options.HostUIDMapping = false
+ options.HostGIDMapping = false
return &options, nil
}
@@ -635,37 +653,6 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) {
return sysctl, nil
}
-// SELinuxKVMLabel returns labels for running kvm isolated containers
-func SELinuxKVMLabel(cLabel string) (string, error) {
- if cLabel == "" {
- // selinux is disabled
- return "", nil
- }
- processLabel, _ := selinux.KVMContainerLabels()
- selinux.ReleaseLabel(processLabel)
- return swapSELinuxLabel(cLabel, processLabel)
-}
-
-// SELinuxInitLabel returns labels for running systemd based containers
-func SELinuxInitLabel(cLabel string) (string, error) {
- if cLabel == "" {
- // selinux is disabled
- return "", nil
- }
- processLabel, _ := selinux.InitContainerLabels()
- selinux.ReleaseLabel(processLabel)
- return swapSELinuxLabel(cLabel, processLabel)
-}
-
-func swapSELinuxLabel(cLabel, processLabel string) (string, error) {
- dcon, err := selinux.NewContext(cLabel)
- if err != nil {
- return "", err
- }
- scon, err := selinux.NewContext(processLabel)
- if err != nil {
- return "", err
- }
- dcon["type"] = scon["type"]
- return dcon.Get(), nil
+func DefaultContainerConfig() *config.Config {
+ return containerConfig
}
diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go
index 63d5072c6..571ce6115 100644
--- a/pkg/varlinkapi/create.go
+++ b/pkg/varlinkapi/create.go
@@ -13,6 +13,7 @@ import (
"syscall"
"time"
+ "github.com/containers/common/pkg/sysinfo"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/cmd/podman/parse"
"github.com/containers/libpod/libpod"
@@ -28,7 +29,6 @@ import (
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/seccomp"
cc "github.com/containers/libpod/pkg/spec"
- "github.com/containers/libpod/pkg/sysinfo"
systemdGen "github.com/containers/libpod/pkg/systemd/generate"
"github.com/containers/libpod/pkg/util"
"github.com/docker/go-connections/nat"
diff --git a/pkg/varlinkapi/intermediate_varlink.go b/pkg/varlinkapi/intermediate_varlink.go
index 21c57d4f4..bd0c45b33 100644
--- a/pkg/varlinkapi/intermediate_varlink.go
+++ b/pkg/varlinkapi/intermediate_varlink.go
@@ -331,7 +331,7 @@ func intFromVarlink(v *int64, flagName string, defaultValue *int) CRInt {
// structure.
func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults {
// FIXME this will need to be fixed!!!!! With containers conf
- //defaultContainerConfig := cliconfig.GetDefaultConfig()
+ //containerConfig := cliconfig.GetDefaultConfig()
// TODO | WARN
// We do not get a default network over varlink. Unlike the other default values for some cli
// elements, it seems it gets set to the default anyway.
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at
index 42ec028d0..1c8da0c2f 100644
--- a/test/apiv2/10-images.at
+++ b/test/apiv2/10-images.at
@@ -7,15 +7,15 @@
podman pull -q $IMAGE
t GET libpod/images/json 200 \
- .[0].Id~[0-9a-f]\\{64\\}
-iid=$(jq -r '.[0].Id' <<<"$output")
+ .[0].ID~[0-9a-f]\\{64\\}
+iid=$(jq -r '.[0].ID' <<<"$output")
t GET libpod/images/$iid/exists 204
t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
# FIXME: compare to actual podman info
t GET libpod/images/json 200 \
- .[0].Id=${iid}
+ .[0].ID=${iid}
t GET libpod/images/$iid/json 200 \
.Id=$iid \
diff --git a/test/e2e/build_test.go b/test/e2e/build_test.go
index 9e41fd231..76651283a 100644
--- a/test/e2e/build_test.go
+++ b/test/e2e/build_test.go
@@ -177,6 +177,7 @@ var _ = Describe("Podman build", func() {
})
It("podman Test PATH in built image", func() {
+ Skip(v2fail) // Run error - we don't set data from the image (i.e., PATH) yet
path := "/tmp:/bin:/usr/bin:/usr/sbin"
session := podmanTest.PodmanNoCache([]string{
"build", "-f", "build/basicalpine/Containerfile.path", "-t", "test-path",
diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go
index d93ee8d3a..68f733b41 100644
--- a/test/e2e/common_test.go
+++ b/test/e2e/common_test.go
@@ -1,7 +1,6 @@
package integration
import (
- "encoding/json"
"fmt"
"io/ioutil"
"math/rand"
@@ -14,7 +13,6 @@ import (
"testing"
"time"
- "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/rootless"
@@ -22,9 +20,10 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/reexec"
"github.com/containers/storage/pkg/stringid"
+ jsoniter "github.com/json-iterator/go"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
- "github.com/onsi/gomega/gexec"
+ . "github.com/onsi/gomega/gexec"
"github.com/pkg/errors"
)
@@ -315,7 +314,7 @@ func (p *PodmanTestIntegration) createArtifact(image string) {
// image and returns json
func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData {
var i []inspect.ImageData
- err := json.Unmarshal(s.Out.Contents(), &i)
+ err := jsoniter.Unmarshal(s.Out.Contents(), &i)
Expect(err).To(BeNil())
return i
}
@@ -325,7 +324,7 @@ func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectCo
cmd := []string{"inspect", name}
session := p.Podman(cmd)
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
return session.InspectContainerToJSON()
}
@@ -420,7 +419,7 @@ func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegrat
podmanOptions := p.MakeOptions(args, false, false)
fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " "))
command := exec.Command(p.PodmanBinary, podmanOptions...)
- session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
+ session, err := Start(command, GinkgoWriter, GinkgoWriter)
if err != nil {
Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " ")))
}
@@ -495,15 +494,15 @@ func (p *PodmanTestIntegration) PullImage(image string) error {
// container and returns json
func (s *PodmanSessionIntegration) InspectContainerToJSON() []define.InspectContainerData {
var i []define.InspectContainerData
- err := json.Unmarshal(s.Out.Contents(), &i)
+ err := jsoniter.Unmarshal(s.Out.Contents(), &i)
Expect(err).To(BeNil())
return i
}
// InspectPodToJSON takes the sessions output from a pod inspect and returns json
-func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect {
- var i libpod.PodInspect
- err := json.Unmarshal(s.Out.Contents(), &i)
+func (s *PodmanSessionIntegration) InspectPodToJSON() define.InspectPodData {
+ var i define.InspectPodData
+ err := jsoniter.Unmarshal(s.Out.Contents(), &i)
Expect(err).To(BeNil())
return i
}
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 49a47c7da..0e1850614 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -23,4 +23,8 @@ var (
// This image has a bogus/invalid seccomp profile which should
// yield a json error when being read.
alpineBogusSeccomp = "docker.io/libpod/alpine-with-bogus-seccomp:label"
+
+ // v2fail is a temporary variable to help us track
+ // tests that fail in v2
+ v2fail = "does not pass integration tests with v2 podman"
)
diff --git a/test/e2e/containers_conf_test.go b/test/e2e/containers_conf_test.go
index a2ef7eb4a..b984a35f3 100644
--- a/test/e2e/containers_conf_test.go
+++ b/test/e2e/containers_conf_test.go
@@ -23,6 +23,7 @@ var _ = Describe("Podman run", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/cp_test.go b/test/e2e/cp_test.go
index b71897cfd..f95f8646c 100644
--- a/test/e2e/cp_test.go
+++ b/test/e2e/cp_test.go
@@ -95,7 +95,7 @@ var _ = Describe("Podman cp", func() {
})
It("podman cp dir to dir", func() {
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir")
+ testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir1")
session := podmanTest.Podman([]string{"create", ALPINE, "ls", "/foodir"})
session.WaitWithDefaultTimeout()
@@ -104,6 +104,7 @@ var _ = Describe("Podman cp", func() {
err := os.Mkdir(testDirPath, 0755)
Expect(err).To(BeNil())
+ defer os.RemoveAll(testDirPath)
session = podmanTest.Podman([]string{"cp", testDirPath, name + ":/foodir"})
session.WaitWithDefaultTimeout()
@@ -137,8 +138,6 @@ var _ = Describe("Podman cp", func() {
res, err := cmd.Output()
Expect(err).To(BeNil())
Expect(len(res)).To(Equal(0))
-
- os.RemoveAll(testDirPath)
})
It("podman cp stdin/stdout", func() {
@@ -147,9 +146,10 @@ var _ = Describe("Podman cp", func() {
Expect(session.ExitCode()).To(Equal(0))
name := session.OutputToString()
- testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir")
+ testDirPath := filepath.Join(podmanTest.RunRoot, "TestDir2")
err := os.Mkdir(testDirPath, 0755)
Expect(err).To(BeNil())
+ defer os.RemoveAll(testDirPath)
cmd := exec.Command("tar", "-zcvf", "file.tar.gz", testDirPath)
_, err = cmd.Output()
Expect(err).To(BeNil())
@@ -168,7 +168,6 @@ var _ = Describe("Podman cp", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- os.RemoveAll(testDirPath)
os.Remove("file.tar.gz")
})
@@ -184,9 +183,10 @@ var _ = Describe("Podman cp", func() {
path, err := os.Getwd()
Expect(err).To(BeNil())
- testDirPath := filepath.Join(path, "TestDir")
+ testDirPath := filepath.Join(path, "TestDir3")
err = os.Mkdir(testDirPath, 0777)
Expect(err).To(BeNil())
+ defer os.RemoveAll(testDirPath)
cmd := exec.Command("tar", "-cvf", "file.tar", testDirPath)
_, err = cmd.Output()
Expect(err).To(BeNil())
@@ -201,7 +201,6 @@ var _ = Describe("Podman cp", func() {
Expect(session.OutputToString()).To(ContainSubstring("file.tar"))
os.Remove("file.tar")
- os.RemoveAll(testDirPath)
})
It("podman cp symlink", func() {
diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go
index 10742a0e8..82346823a 100644
--- a/test/e2e/create_test.go
+++ b/test/e2e/create_test.go
@@ -18,6 +18,7 @@ var _ = Describe("Podman create", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go
index 1486427c5..e26fad51d 100644
--- a/test/e2e/exists_test.go
+++ b/test/e2e/exists_test.go
@@ -6,6 +6,7 @@ import (
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman image|container exists", func() {
@@ -35,17 +36,17 @@ var _ = Describe("Podman image|container exists", func() {
It("podman image exists in local storage by fq name", func() {
session := podmanTest.Podman([]string{"image", "exists", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman image exists in local storage by short name", func() {
session := podmanTest.Podman([]string{"image", "exists", "alpine"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman image does not exist in local storage", func() {
session := podmanTest.Podman([]string{"image", "exists", "alpine9999"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(1))
+ Expect(session).Should(Exit(1))
})
It("podman container exists in local storage by name", func() {
setup := podmanTest.RunTopContainer("foobar")
@@ -54,17 +55,17 @@ var _ = Describe("Podman image|container exists", func() {
session := podmanTest.Podman([]string{"container", "exists", "foobar"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman container exists in local storage by container ID", func() {
setup := podmanTest.RunTopContainer("")
setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
+ Expect(setup).Should(Exit(0))
cid := setup.OutputToString()
session := podmanTest.Podman([]string{"container", "exists", cid})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman container exists in local storage by short container ID", func() {
setup := podmanTest.RunTopContainer("")
@@ -74,46 +75,46 @@ var _ = Describe("Podman image|container exists", func() {
session := podmanTest.Podman([]string{"container", "exists", cid})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman container does not exist in local storage", func() {
session := podmanTest.Podman([]string{"container", "exists", "foobar"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(1))
+ Expect(session).Should(Exit(1))
})
It("podman pod exists in local storage by name", func() {
- setup, rc, _ := podmanTest.CreatePod("foobar")
+ setup, _, _ := podmanTest.CreatePod("foobar")
setup.WaitWithDefaultTimeout()
- Expect(rc).To(Equal(0))
+ Expect(setup).Should(Exit(0))
session := podmanTest.Podman([]string{"pod", "exists", "foobar"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman pod exists in local storage by container ID", func() {
- setup, rc, podID := podmanTest.CreatePod("")
+ setup, _, podID := podmanTest.CreatePod("")
setup.WaitWithDefaultTimeout()
- Expect(rc).To(Equal(0))
+ Expect(setup).Should(Exit(0))
session := podmanTest.Podman([]string{"pod", "exists", podID})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman pod exists in local storage by short container ID", func() {
- setup, rc, podID := podmanTest.CreatePod("")
+ setup, _, podID := podmanTest.CreatePod("")
setup.WaitWithDefaultTimeout()
- Expect(rc).To(Equal(0))
+ Expect(setup).Should(Exit(0))
session := podmanTest.Podman([]string{"pod", "exists", podID[0:12]})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman pod does not exist in local storage", func() {
// The exit code for non-existing pod is incorrect (125 vs 1)
SkipIfRemote()
session := podmanTest.Podman([]string{"pod", "exists", "foobar"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(1))
+ Expect(session).Should(Exit(1))
})
})
diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go
index 389f2c822..e4f487634 100644
--- a/test/e2e/generate_kube_test.go
+++ b/test/e2e/generate_kube_test.go
@@ -21,6 +21,7 @@ var _ = Describe("Podman generate kube", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index 8b6b679a5..d7295b67a 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -10,6 +10,7 @@ import (
"github.com/docker/go-units"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman images", func() {
@@ -38,7 +39,7 @@ var _ = Describe("Podman images", func() {
It("podman images", func() {
session := podmanTest.Podman([]string{"images"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue())
Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue())
@@ -47,11 +48,11 @@ var _ = Describe("Podman images", func() {
It("podman images with no images prints header", func() {
rmi := podmanTest.PodmanNoCache([]string{"rmi", "-a"})
rmi.WaitWithDefaultTimeout()
- Expect(rmi.ExitCode()).To(Equal(0))
+ Expect(rmi).Should(Exit(0))
session := podmanTest.PodmanNoCache([]string{"images"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(1))
Expect(session.LineInOutputContains("REPOSITORY")).To(BeTrue())
})
@@ -59,7 +60,7 @@ var _ = Describe("Podman images", func() {
It("podman image List", func() {
session := podmanTest.Podman([]string{"image", "list"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue())
Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue())
@@ -70,15 +71,15 @@ var _ = Describe("Podman images", func() {
podmanTest.RestoreAllArtifacts()
session := podmanTest.PodmanNoCache([]string{"tag", ALPINE, "foo:a", "foo:b", "foo:c"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
// tag "foo:c" to "bar:{a,b}"
session = podmanTest.PodmanNoCache([]string{"tag", "foo:c", "bar:a", "bar:b"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
// check all previous and the newly tagged images
session = podmanTest.PodmanNoCache([]string{"images"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session.LineInOutputContainsTag("docker.io/library/alpine", "latest")
session.LineInOutputContainsTag("docker.io/library/busybox", "glibc")
session.LineInOutputContainsTag("foo", "a")
@@ -88,14 +89,14 @@ var _ = Describe("Podman images", func() {
session.LineInOutputContainsTag("bar", "b")
session = podmanTest.PodmanNoCache([]string{"images", "-qn"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically("==", 2))
})
It("podman images with digests", func() {
session := podmanTest.Podman([]string{"images", "--digests"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
Expect(session.LineInOuputStartsWith("docker.io/library/alpine")).To(BeTrue())
Expect(session.LineInOuputStartsWith("docker.io/library/busybox")).To(BeTrue())
@@ -104,14 +105,14 @@ var _ = Describe("Podman images", func() {
It("podman empty images list in JSON format", func() {
session := podmanTest.Podman([]string{"images", "--format=json", "not-existing-image"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
It("podman images in JSON format", func() {
session := podmanTest.Podman([]string{"images", "--format=json"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
@@ -119,13 +120,13 @@ var _ = Describe("Podman images", func() {
formatStr := "{{.ID}}\t{{.Created}}\t{{.CreatedAt}}\t{{.CreatedSince}}\t{{.CreatedTime}}"
session := podmanTest.Podman([]string{"images", fmt.Sprintf("--format=%s", formatStr)})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman images with short options", func() {
session := podmanTest.Podman([]string{"images", "-qn"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 1))
})
@@ -133,19 +134,19 @@ var _ = Describe("Podman images", func() {
podmanTest.RestoreAllArtifacts()
session := podmanTest.PodmanNoCache([]string{"images", "-q", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(1))
session = podmanTest.PodmanNoCache([]string{"tag", ALPINE, "foo:a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"tag", BB, "foo:b"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(2))
})
@@ -156,24 +157,24 @@ var _ = Describe("Podman images", func() {
podmanTest.RestoreAllArtifacts()
result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "reference=docker.io*"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray())).To(Equal(2))
retapline := podmanTest.PodmanNoCache([]string{"images", "-f", "reference=a*pine"})
retapline.WaitWithDefaultTimeout()
- Expect(retapline.ExitCode()).To(Equal(0))
+ Expect(retapline).Should(Exit(0))
Expect(len(retapline.OutputToStringArray())).To(Equal(2))
Expect(retapline.LineInOutputContains("alpine")).To(BeTrue())
retapline = podmanTest.PodmanNoCache([]string{"images", "-f", "reference=alpine"})
retapline.WaitWithDefaultTimeout()
- Expect(retapline.ExitCode()).To(Equal(0))
+ Expect(retapline).Should(Exit(0))
Expect(len(retapline.OutputToStringArray())).To(Equal(2))
Expect(retapline.LineInOutputContains("alpine")).To(BeTrue())
retnone := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "reference=bogus"})
retnone.WaitWithDefaultTimeout()
- Expect(retnone.ExitCode()).To(Equal(0))
+ Expect(retnone).Should(Exit(0))
Expect(len(retnone.OutputToStringArray())).To(Equal(0))
})
@@ -187,7 +188,7 @@ RUN apk update && apk add man
podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false")
result := podmanTest.Podman([]string{"images", "-q", "-f", "before=foobar.com/before:latest"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray()) >= 1).To(BeTrue())
})
@@ -198,14 +199,14 @@ RUN apk update && apk add man
podmanTest.RestoreAllArtifacts()
rmi := podmanTest.PodmanNoCache([]string{"rmi", "busybox"})
rmi.WaitWithDefaultTimeout()
- Expect(rmi.ExitCode()).To(Equal(0))
+ Expect(rmi).Should(Exit(0))
dockerfile := `FROM docker.io/library/alpine:latest
`
podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false")
result := podmanTest.PodmanNoCache([]string{"images", "-q", "-f", "after=docker.io/library/alpine:latest"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray())).To(Equal(0))
})
@@ -216,14 +217,14 @@ RUN apk update && apk add man
podmanTest.RestoreAllArtifacts()
rmi := podmanTest.PodmanNoCache([]string{"image", "rm", "busybox"})
rmi.WaitWithDefaultTimeout()
- Expect(rmi.ExitCode()).To(Equal(0))
+ Expect(rmi).Should(Exit(0))
dockerfile := `FROM docker.io/library/alpine:latest
`
podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false")
result := podmanTest.PodmanNoCache([]string{"image", "list", "-q", "-f", "after=docker.io/library/alpine:latest"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray())).To(Equal(0))
})
@@ -237,7 +238,7 @@ RUN apk update && apk add man
podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false")
result := podmanTest.Podman([]string{"images", "-q", "-f", "dangling=true"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(len(result.OutputToStringArray())).To(Equal(0))
})
@@ -247,13 +248,13 @@ RUN apk update && apk add man
}
session := podmanTest.Podman([]string{"inspect", "--format=json", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
imageData := session.InspectImageJSON()
- result := podmanTest.Podman([]string{"images", fmt.Sprintf("sha256:%s", imageData[0].ID)})
+ result := podmanTest.Podman([]string{"images", "sha256:" + imageData[0].ID})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
})
It("podman check for image with sha256: prefix", func() {
@@ -262,13 +263,13 @@ RUN apk update && apk add man
}
session := podmanTest.Podman([]string{"image", "inspect", "--format=json", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
imageData := session.InspectImageJSON()
result := podmanTest.Podman([]string{"image", "ls", fmt.Sprintf("sha256:%s", imageData[0].ID)})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
})
It("podman images sort by values", func() {
@@ -276,7 +277,7 @@ RUN apk update && apk add man
f := fmt.Sprintf("{{.%s}}", format)
session := podmanTest.Podman([]string{"images", "--sort", value, "--format", f})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(result))
+ Expect(session).Should(Exit(result))
return session.OutputToStringArray()
}
@@ -297,7 +298,9 @@ RUN apk update && apk add man
return size1 < size2
})).To(BeTrue())
sortedArr = sortValueTest("tag", 0, "Tag")
- Expect(sort.SliceIsSorted(sortedArr, func(i, j int) bool { return sortedArr[i] < sortedArr[j] })).To(BeTrue())
+ Expect(sort.SliceIsSorted(sortedArr,
+ func(i, j int) bool { return sortedArr[i] < sortedArr[j] })).
+ To(BeTrue())
sortValueTest("badvalue", 125, "Tag")
sortValueTest("id", 125, "badvalue")
@@ -316,12 +319,12 @@ ENV foo=bar
podmanTest.BuildImage(dockerfile, "test", "true")
session := podmanTest.PodmanNoCache([]string{"images"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(4))
session2 := podmanTest.PodmanNoCache([]string{"images", "--all"})
session2.WaitWithDefaultTimeout()
- Expect(session2.ExitCode()).To(Equal(0))
+ Expect(session2).Should(Exit(0))
Expect(len(session2.OutputToStringArray())).To(Equal(6))
})
@@ -334,7 +337,7 @@ LABEL "com.example.vendor"="Example Vendor"
podmanTest.BuildImage(dockerfile, "test", "true")
session := podmanTest.Podman([]string{"images", "-f", "label=version=1.0"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(2))
})
@@ -356,52 +359,52 @@ LABEL "com.example.vendor"="Example Vendor"
session := podmanTest.Podman([]string{"images", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output := session.OutputToString()
Expect(output).To(Not(MatchRegexp("<missing>")))
Expect(output).To(Not(MatchRegexp("error")))
session = podmanTest.Podman([]string{"image", "tree", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(MatchRegexp("No Image Layers"))
session = podmanTest.Podman([]string{"history", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(Not(MatchRegexp("error")))
session = podmanTest.Podman([]string{"history", "--quiet", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(6))
session = podmanTest.Podman([]string{"image", "list", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(Not(MatchRegexp("<missing>")))
Expect(output).To(Not(MatchRegexp("error")))
session = podmanTest.Podman([]string{"image", "list"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(Not(MatchRegexp("<missing>")))
Expect(output).To(Not(MatchRegexp("error")))
session = podmanTest.Podman([]string{"inspect", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(Not(MatchRegexp("<missing>")))
Expect(output).To(Not(MatchRegexp("error")))
session = podmanTest.Podman([]string{"inspect", "--format", "{{.RootFS.Layers}}", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
output = session.OutputToString()
Expect(output).To(Equal("[]"))
})
@@ -413,11 +416,11 @@ LABEL "com.example.vendor"="Example Vendor"
podmanTest.BuildImage(dockerfile, "foobar.com/before:latest", "false")
result := podmanTest.Podman([]string{"images", "-f", "readonly=true"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
result1 := podmanTest.Podman([]string{"images", "--filter", "readonly=false"})
result1.WaitWithDefaultTimeout()
- Expect(result1.ExitCode()).To(Equal(0))
+ Expect(result1).Should(Exit(0))
Expect(result.OutputToStringArray()).To(Not(Equal(result1.OutputToStringArray())))
})
diff --git a/test/e2e/info_test.go b/test/e2e/info_test.go
index 446dbc16e..7cb299e0f 100644
--- a/test/e2e/info_test.go
+++ b/test/e2e/info_test.go
@@ -1,8 +1,13 @@
package integration
import (
+ "fmt"
+ "io/ioutil"
"os"
+ "os/exec"
+ "path/filepath"
+ "github.com/containers/libpod/pkg/rootless"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -37,22 +42,54 @@ var _ = Describe("Podman Info", func() {
Expect(session.ExitCode()).To(Equal(0))
})
- It("podman system info json output", func() {
- session := podmanTest.Podman([]string{"system", "info", "--format=json"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- })
- It("podman info --format JSON GO template", func() {
- session := podmanTest.Podman([]string{"info", "--format", "{{ json .}}"})
+ It("podman info --format GO template", func() {
+ session := podmanTest.Podman([]string{"info", "--format", "{{.Store.GraphRoot}}"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- Expect(session.IsJSONOutputValid()).To(BeTrue())
})
It("podman info --format GO template", func() {
- session := podmanTest.Podman([]string{"info", "--format", "{{ .Store.GraphRoot }}"})
+ session := podmanTest.Podman([]string{"info", "--format", "{{.Registries}}"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring("registry"))
+ })
+
+ It("podman info rootless storage path", func() {
+ if !rootless.IsRootless() {
+ Skip("test of rootless_storage_path is only meaningful as rootless")
+ }
+ SkipIfRemote()
+ oldHOME, hasHOME := os.LookupEnv("HOME")
+ defer func() {
+ if hasHOME {
+ os.Setenv("HOME", oldHOME)
+ } else {
+ os.Unsetenv("HOME")
+ }
+ }()
+ os.Setenv("HOME", podmanTest.TempDir)
+ configPath := filepath.Join(os.Getenv("HOME"), ".config", "containers", "storage.conf")
+ err := os.RemoveAll(filepath.Dir(configPath))
+ Expect(err).To(BeNil())
+
+ err = os.MkdirAll(filepath.Dir(configPath), os.ModePerm)
+ Expect(err).To(BeNil())
+
+ rootlessStoragePath := `"/tmp/$HOME/$USER/$UID"`
+ driver := `"overlay"`
+ storageOpt := `"/usr/bin/fuse-overlayfs"`
+ storageConf := []byte(fmt.Sprintf("[storage]\ndriver=%s\nrootless_storage_path=%s\n[storage.options]\nmount_program=%s", driver, rootlessStoragePath, storageOpt))
+ err = ioutil.WriteFile(configPath, storageConf, os.ModePerm)
+ Expect(err).To(BeNil())
+
+ expect := filepath.Join("/tmp", os.Getenv("HOME"), os.Getenv("USER"), os.Getenv("UID"))
+ podmanPath := podmanTest.PodmanTest.PodmanBinary
+ cmd := exec.Command(podmanPath, "info", "--format", "{{.Store.GraphRoot}}")
+ out, err := cmd.CombinedOutput()
+ fmt.Println(string(out))
+ Expect(err).To(BeNil())
+ Expect(string(out)).To(ContainSubstring(expect))
})
})
diff --git a/test/e2e/login_logout_test.go b/test/e2e/login_logout_test.go
index 3f76daa67..dd35d8489 100644
--- a/test/e2e/login_logout_test.go
+++ b/test/e2e/login_logout_test.go
@@ -32,6 +32,7 @@ var _ = Describe("Podman login and logout", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/manifest_test.go b/test/e2e/manifest_test.go
new file mode 100644
index 000000000..9b5a24771
--- /dev/null
+++ b/test/e2e/manifest_test.go
@@ -0,0 +1,101 @@
+package integration
+
+import (
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman manifest", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ const (
+ imageList = "docker://k8s.gcr.io/pause:3.1"
+ imageListInstance = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
+ imageListARM64InstanceDigest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39"
+ imageListAMD64InstanceDigest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610"
+ imageListARMInstanceDigest = "sha256:c84b0a3a07b628bc4d62e5047d0f8dff80f7c00979e1e28a821a033ecda8fe53"
+ imageListPPC64LEInstanceDigest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990"
+ imageListS390XInstanceDigest = "sha256:882a20ee0df7399a445285361d38b711c299ca093af978217112c73803546d5e"
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+ It("podman manifest create", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman manifest add", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
+ It("podman manifest add one", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--arch=arm64", "foo", imageListInstance})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest))
+ })
+
+ It("podman manifest add --all", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--all", "foo", imageList})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListAMD64InstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListARMInstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListARM64InstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListPPC64LEInstanceDigest))
+ Expect(session.OutputToString()).To(ContainSubstring(imageListS390XInstanceDigest))
+ })
+
+ It("podman manifest add --os", func() {
+ session := podmanTest.Podman([]string{"manifest", "create", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "add", "--os", "bar", "foo", imageList})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ session = podmanTest.Podman([]string{"manifest", "inspect", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(session.OutputToString()).To(ContainSubstring(`"os": "bar"`))
+ })
+})
diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go
index 7eccaa9ab..19dabced7 100644
--- a/test/e2e/network_create_test.go
+++ b/test/e2e/network_create_test.go
@@ -76,6 +76,7 @@ var _ = Describe("Podman network create", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
SkipIfRootless()
tempdir, err = CreateTempDirInTempDir()
if err != nil {
diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go
index 440d307b5..2cb7eb144 100644
--- a/test/e2e/network_test.go
+++ b/test/e2e/network_test.go
@@ -34,6 +34,7 @@ var _ = Describe("Podman network", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go
index 39e08e2e8..149a2e28a 100644
--- a/test/e2e/pause_test.go
+++ b/test/e2e/pause_test.go
@@ -2,7 +2,10 @@ package integration
import (
"fmt"
+ "io/ioutil"
"os"
+ "path/filepath"
+ "strings"
"github.com/containers/libpod/pkg/cgroups"
. "github.com/containers/libpod/test/utils"
@@ -17,8 +20,8 @@ var _ = Describe("Podman pause", func() {
podmanTest *PodmanTestIntegration
)
- pausedState := "Paused"
- createdState := "Created"
+ pausedState := "paused"
+ createdState := "created"
BeforeEach(func() {
SkipIfRootless()
@@ -31,7 +34,13 @@ var _ = Describe("Podman pause", func() {
Expect(err).To(BeNil())
if cgroupsv2 {
- _, err := os.Stat("/sys/fs/cgroup/cgroup.freeze")
+ b, err := ioutil.ReadFile("/proc/self/cgroup")
+ if err != nil {
+ Skip("cannot read self cgroup")
+ }
+
+ path := filepath.Join("/sys/fs/cgroup", strings.TrimSuffix(strings.Replace(string(b), "0::", "", 1), "\n"), "cgroup.freeze")
+ _, err = os.Stat(path)
if err != nil {
Skip("freezer controller not available on the current kernel")
}
@@ -72,7 +81,7 @@ var _ = Describe("Podman pause", func() {
Expect(result).To(ExitWithError())
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(createdState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(createdState))
})
It("podman pause a running container by id", func() {
@@ -85,7 +94,7 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"unpause", cid})
result.WaitWithDefaultTimeout()
@@ -102,7 +111,7 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"container", "unpause", cid})
result.WaitWithDefaultTimeout()
@@ -133,14 +142,14 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"rm", cid})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(2))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
})
@@ -155,7 +164,7 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"rm", "--force", cid})
result.WaitWithDefaultTimeout()
@@ -175,14 +184,14 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"stop", cid})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(ContainSubstring(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(ContainSubstring(pausedState))
result = podmanTest.Podman([]string{"unpause", cid})
result.WaitWithDefaultTimeout()
@@ -211,7 +220,7 @@ var _ = Describe("Podman pause", func() {
Expect(result.ExitCode()).To(Equal(0))
Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
- Expect(podmanTest.GetContainerStatus()).To(Equal(pausedState))
+ Expect(strings.ToLower(podmanTest.GetContainerStatus())).To(Equal(pausedState))
result = podmanTest.Podman([]string{"unpause", "test1"})
result.WaitWithDefaultTimeout()
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index 9daf266b8..16f7af55e 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -217,6 +217,7 @@ var _ = Describe("Podman generate kube", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/pod_infra_container_test.go b/test/e2e/pod_infra_container_test.go
index c8072f308..3cc6fa9e8 100644
--- a/test/e2e/pod_infra_container_test.go
+++ b/test/e2e/pod_infra_container_test.go
@@ -94,12 +94,17 @@ var _ = Describe("Podman pod create", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- check := podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--ns", "--format", "{{.IPC}} {{.NET}}"})
+ check := podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--ns", "--format", "{{.Namespaces.IPC}} {{.Namespaces.NET}}"})
check.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(len(check.OutputToStringArray())).To(Equal(2))
Expect(check.OutputToStringArray()[0]).To(Equal(check.OutputToStringArray()[1]))
+ check = podmanTest.Podman([]string{"ps", "-a", "--no-trunc", "--ns", "--format", "{{.IPC}} {{.NET}}"})
+ check.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(check.OutputToStringArray())).To(Equal(2))
+ Expect(check.OutputToStringArray()[0]).To(Equal(check.OutputToStringArray()[1]))
})
It("podman pod correctly sets up NetNS", func() {
@@ -235,12 +240,18 @@ var _ = Describe("Podman pod create", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.PIDNS}}"})
+ check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.Namespaces.PIDNS}}"})
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(0))
outputArray := check.OutputToStringArray()
Expect(len(outputArray)).To(Equal(2))
+ check = podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.PIDNS}}"})
+ check.WaitWithDefaultTimeout()
+ Expect(check.ExitCode()).To(Equal(0))
+ outputArray = check.OutputToStringArray()
+ Expect(len(outputArray)).To(Equal(2))
+
PID1 := outputArray[0]
PID2 := outputArray[1]
Expect(PID1).To(Not(Equal(PID2)))
diff --git a/test/e2e/pod_inspect_test.go b/test/e2e/pod_inspect_test.go
index d86c36f58..f87bbe047 100644
--- a/test/e2e/pod_inspect_test.go
+++ b/test/e2e/pod_inspect_test.go
@@ -54,8 +54,7 @@ var _ = Describe("Podman pod inspect", func() {
inspect.WaitWithDefaultTimeout()
Expect(inspect.ExitCode()).To(Equal(0))
Expect(inspect.IsJSONOutputValid()).To(BeTrue())
- // FIXME sujil, disabled for now
- //podData := inspect.InspectPodToJSON()
- //Expect(podData.Config.ID).To(Equal(podid))
+ podData := inspect.InspectPodToJSON()
+ Expect(podData.ID).To(Equal(podid))
})
})
diff --git a/test/e2e/pod_pause_test.go b/test/e2e/pod_pause_test.go
index 73707926d..7067c9a87 100644
--- a/test/e2e/pod_pause_test.go
+++ b/test/e2e/pod_pause_test.go
@@ -15,7 +15,7 @@ var _ = Describe("Podman pod pause", func() {
podmanTest *PodmanTestIntegration
)
- pausedState := "Paused"
+ pausedState := "paused"
BeforeEach(func() {
SkipIfRootless()
diff --git a/test/e2e/pod_pod_namespaces.go b/test/e2e/pod_pod_namespaces.go
index 83c877f5a..09f716806 100644
--- a/test/e2e/pod_pod_namespaces.go
+++ b/test/e2e/pod_pod_namespaces.go
@@ -49,7 +49,7 @@ var _ = Describe("Podman pod create", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.IPC}} {{.UTS}} {{.NET}}"})
+ check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.Namespaces.IPC}} {{.Namespaces.UTS}} {{.Namespaces.NET}}"})
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(0))
outputArray := check.OutputToStringArray()
@@ -76,7 +76,7 @@ var _ = Describe("Podman pod create", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.PIDNS}}"})
+ check := podmanTest.Podman([]string{"ps", "-a", "--ns", "--format", "{{.Namespaces.PIDNS}}"})
check.WaitWithDefaultTimeout()
Expect(check.ExitCode()).To(Equal(0))
outputArray := check.OutputToStringArray()
diff --git a/test/e2e/pod_prune_test.go b/test/e2e/pod_prune_test.go
index 389d3cb27..d98383331 100644
--- a/test/e2e/pod_prune_test.go
+++ b/test/e2e/pod_prune_test.go
@@ -36,7 +36,7 @@ var _ = Describe("Podman pod prune", func() {
_, ec, _ := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
- result := podmanTest.Podman([]string{"pod", "prune"})
+ result := podmanTest.Podman([]string{"pod", "prune", "--force"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
})
@@ -49,7 +49,7 @@ var _ = Describe("Podman pod prune", func() {
ec2.WaitWithDefaultTimeout()
Expect(ec2.ExitCode()).To(Equal(0))
- result := podmanTest.Podman([]string{"pod", "prune"})
+ result := podmanTest.Podman([]string{"pod", "prune", "-f"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To((Equal(0)))
@@ -65,7 +65,7 @@ var _ = Describe("Podman pod prune", func() {
_, ec2, _ := podmanTest.RunLsContainerInPod("", podid)
Expect(ec2).To(Equal(0))
- result := podmanTest.Podman([]string{"pod", "prune"})
+ result := podmanTest.Podman([]string{"pod", "prune", "-f"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(0))
@@ -73,21 +73,4 @@ var _ = Describe("Podman pod prune", func() {
result.WaitWithDefaultTimeout()
Expect(len(result.OutputToStringArray())).To(Equal(0))
})
-
- It("podman pod prune -f does remove a running container", func() {
- _, ec, podid := podmanTest.CreatePod("")
- Expect(ec).To(Equal(0))
-
- session := podmanTest.RunTopContainerInPod("", podid)
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- result := podmanTest.Podman([]string{"pod", "prune", "-f"})
- result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
-
- result = podmanTest.Podman([]string{"ps", "-q"})
- result.WaitWithDefaultTimeout()
- Expect(result.OutputToString()).To(BeEmpty())
- })
})
diff --git a/test/e2e/pod_ps_test.go b/test/e2e/pod_ps_test.go
index 551ad3818..5f8712a7a 100644
--- a/test/e2e/pod_ps_test.go
+++ b/test/e2e/pod_ps_test.go
@@ -95,6 +95,7 @@ var _ = Describe("Podman ps", func() {
Expect(result.OutputToString()).To(ContainSubstring(podid2))
Expect(result.OutputToString()).To(Not(ContainSubstring(podid1)))
})
+
It("podman pod ps id filter flag", func() {
_, ec, podid := podmanTest.CreatePod("")
Expect(ec).To(Equal(0))
@@ -142,7 +143,7 @@ var _ = Describe("Podman ps", func() {
_, ec, _ = podmanTest.RunLsContainerInPod("test2", podid)
Expect(ec).To(Equal(0))
- session = podmanTest.Podman([]string{"pod", "ps", "--format={{.ContainerInfo}}", "--ctr-names"})
+ session = podmanTest.Podman([]string{"pod", "ps", "--format={{.ContainerNames}}", "--ctr-names"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring("test1"))
@@ -227,4 +228,18 @@ var _ = Describe("Podman ps", func() {
Expect(session.OutputToString()).To(ContainSubstring(podid2))
Expect(session.OutputToString()).To(Not(ContainSubstring(podid3)))
})
+
+ It("pod no infra should ps", func() {
+ session := podmanTest.Podman([]string{"pod", "create", "--infra=false"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ ps := podmanTest.Podman([]string{"pod", "ps"})
+ ps.WaitWithDefaultTimeout()
+ Expect(ps.ExitCode()).To(Equal(0))
+
+ infra := podmanTest.Podman([]string{"pod", "ps", "--format", "{{.InfraId}}"})
+ infra.WaitWithDefaultTimeout()
+ Expect(len(infra.OutputToString())).To(BeZero())
+ })
})
diff --git a/test/e2e/port_test.go b/test/e2e/port_test.go
index 5bb86d558..ce31c9ad2 100644
--- a/test/e2e/port_test.go
+++ b/test/e2e/port_test.go
@@ -20,6 +20,7 @@ var _ = Describe("Podman port", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go
index 672b0e103..466a4f739 100644
--- a/test/e2e/prune_test.go
+++ b/test/e2e/prune_test.go
@@ -87,7 +87,7 @@ var _ = Describe("Podman prune", func() {
Expect(podmanTest.NumberOfContainers()).To(Equal(0))
})
- It("podman image prune none images", func() {
+ It("podman image prune skip cache images", func() {
SkipIfRemote()
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
@@ -105,10 +105,35 @@ var _ = Describe("Podman prune", func() {
after.WaitWithDefaultTimeout()
Expect(none.ExitCode()).To(Equal(0))
hasNoneAfter, _ := after.GrepString("<none>")
- Expect(hasNoneAfter).To(BeFalse())
+ Expect(hasNoneAfter).To(BeTrue())
Expect(len(after.OutputToStringArray()) > 1).To(BeTrue())
})
+ It("podman image prune dangling images", func() {
+ SkipIfRemote()
+ podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
+ podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
+
+ none := podmanTest.Podman([]string{"images", "-a"})
+ none.WaitWithDefaultTimeout()
+ Expect(none.ExitCode()).To(Equal(0))
+ hasNone, result := none.GrepString("<none>")
+ Expect(len(result)).To(Equal(2))
+ Expect(hasNone).To(BeTrue())
+
+ prune := podmanTest.Podman([]string{"image", "prune", "-f"})
+ prune.WaitWithDefaultTimeout()
+ Expect(prune.ExitCode()).To(Equal(0))
+
+ after := podmanTest.Podman([]string{"images", "-a"})
+ after.WaitWithDefaultTimeout()
+ Expect(none.ExitCode()).To(Equal(0))
+ hasNoneAfter, result := none.GrepString("<none>")
+ Expect(hasNoneAfter).To(BeTrue())
+ Expect(len(after.OutputToStringArray()) > 1).To(BeTrue())
+ Expect(len(result) > 0).To(BeTrue())
+ })
+
It("podman image prune unused images", func() {
podmanTest.RestoreAllArtifacts()
prune := podmanTest.PodmanNoCache([]string{"image", "prune", "-af"})
@@ -123,6 +148,7 @@ var _ = Describe("Podman prune", func() {
It("podman system image prune unused images", func() {
SkipIfRemote()
+ Skip(v2fail)
podmanTest.RestoreAllArtifacts()
podmanTest.BuildImage(pruneImage, "alpine_bash:latest", "true")
prune := podmanTest.PodmanNoCache([]string{"system", "prune", "-a", "--force"})
@@ -136,6 +162,7 @@ var _ = Describe("Podman prune", func() {
})
It("podman system prune pods", func() {
+ Skip(v2fail)
session := podmanTest.Podman([]string{"pod", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/ps_test.go b/test/e2e/ps_test.go
index adbb9c16c..b987c3ff4 100644
--- a/test/e2e/ps_test.go
+++ b/test/e2e/ps_test.go
@@ -167,6 +167,7 @@ var _ = Describe("Podman ps", func() {
})
It("podman ps namespace flag with go template format", func() {
+ Skip(v2fail)
_, ec, _ := podmanTest.RunLsContainer("test1")
Expect(ec).To(Equal(0))
diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go
index 80e877de1..6c0b01bd5 100644
--- a/test/e2e/rmi_test.go
+++ b/test/e2e/rmi_test.go
@@ -7,6 +7,7 @@ import (
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman rmi", func() {
@@ -36,21 +37,21 @@ var _ = Describe("Podman rmi", func() {
It("podman rmi bogus image", func() {
session := podmanTest.Podman([]string{"rmi", "debian:6.0.10"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(1))
+ Expect(session).Should(Exit(1))
})
It("podman rmi with fq name", func() {
session := podmanTest.PodmanNoCache([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman rmi with short name", func() {
session := podmanTest.PodmanNoCache([]string{"rmi", "alpine"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
@@ -61,7 +62,7 @@ var _ = Describe("Podman rmi", func() {
images := podmanTest.PodmanNoCache([]string{"images"})
images.WaitWithDefaultTimeout()
fmt.Println(images.OutputToStringArray())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
@@ -69,22 +70,22 @@ var _ = Describe("Podman rmi", func() {
podmanTest.RestoreArtifact(nginx)
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
It("podman rmi tagged image", func() {
setup := podmanTest.PodmanNoCache([]string{"images", "-q", ALPINE})
setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
+ Expect(setup).Should(Exit(0))
session := podmanTest.PodmanNoCache([]string{"tag", "alpine", "foo:bar", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
result := podmanTest.PodmanNoCache([]string{"images", "-q", "foo"})
result.WaitWithDefaultTimeout()
- Expect(result.ExitCode()).To(Equal(0))
+ Expect(result).Should(Exit(0))
Expect(result.LineInOutputContains(setup.OutputToString())).To(BeTrue())
})
@@ -92,12 +93,12 @@ var _ = Describe("Podman rmi", func() {
It("podman rmi image with tags by ID cannot be done without force", func() {
setup := podmanTest.PodmanNoCache([]string{"images", "-q", ALPINE})
setup.WaitWithDefaultTimeout()
- Expect(setup.ExitCode()).To(Equal(0))
+ Expect(setup).Should(Exit(0))
alpineId := setup.OutputToString()
session := podmanTest.PodmanNoCache([]string{"tag", "alpine", "foo:bar", "foo"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
// Trying without --force should fail
result := podmanTest.PodmanNoCache([]string{"rmi", alpineId})
@@ -107,80 +108,81 @@ var _ = Describe("Podman rmi", func() {
// With --force it should work
resultForce := podmanTest.PodmanNoCache([]string{"rmi", "-f", alpineId})
resultForce.WaitWithDefaultTimeout()
- Expect(resultForce.ExitCode()).To(Equal(0))
+ Expect(resultForce).Should(Exit(0))
})
It("podman rmi image that is a parent of another image", func() {
SkipIfRemote()
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"run", "--name", "c_test", ALPINE, "true"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"commit", "-q", "c_test", "test"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"rm", "c_test"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(1))
session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToStringArray())).To(Equal(2))
- untaggedImg := session.OutputToStringArray()[1]
+ Expect(session).Should(Exit(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(2),
+ "Output from 'podman images -q -a':'%s'", session.Out.Contents())
+ untaggedImg := session.OutputToStringArray()[0]
session = podmanTest.PodmanNoCache([]string{"rmi", "-f", untaggedImg})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(2))
+ Expect(session).Should(Exit(2), "UntaggedImg is '%s'", untaggedImg)
})
It("podman rmi image that is created from another named imaged", func() {
SkipIfRemote()
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"create", "--name", "c_test1", ALPINE, "true"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"commit", "-q", "c_test1", "test1"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"create", "--name", "c_test2", "test1", "true"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"commit", "-q", "c_test2", "test2"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"rm", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"rmi", "test2"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(2))
})
@@ -188,7 +190,7 @@ var _ = Describe("Podman rmi", func() {
SkipIfRemote()
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
dockerfile := `FROM docker.io/library/alpine:latest
RUN mkdir hello
@@ -207,51 +209,51 @@ var _ = Describe("Podman rmi", func() {
session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
numOfImages := len(session.OutputToStringArray())
session = podmanTest.PodmanNoCache([]string{"rmi", "test2"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(numOfImages - len(session.OutputToStringArray())).To(Equal(2))
session = podmanTest.PodmanNoCache([]string{"rmi", "test"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToStringArray())).To(Equal(1))
podmanTest.BuildImage(dockerfile, "test3", "true")
session = podmanTest.PodmanNoCache([]string{"rmi", ALPINE})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"rmi", "test3"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.PodmanNoCache([]string{"images", "-q", "-a"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(len(session.OutputToString())).To(Equal(0))
})
It("podman rmi -a with no images should be exit 0", func() {
session := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session2 := podmanTest.PodmanNoCache([]string{"rmi", "-fa"})
session2.WaitWithDefaultTimeout()
- Expect(session2.ExitCode()).To(Equal(0))
+ Expect(session2).Should(Exit(0))
})
It("podman rmi -a with parent|child images", func() {
@@ -268,11 +270,11 @@ RUN find $LOCAL
session := podmanTest.PodmanNoCache([]string{"rmi", "-a"})
session.WaitWithDefaultTimeout()
fmt.Println(session.OutputToString())
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
images := podmanTest.PodmanNoCache([]string{"images", "-aq"})
images.WaitWithDefaultTimeout()
- Expect(images.ExitCode()).To(Equal(0))
+ Expect(images).Should(Exit(0))
Expect(len(images.OutputToStringArray())).To(Equal(0))
})
@@ -281,7 +283,7 @@ RUN find $LOCAL
It("podman image rm is the same as rmi", func() {
session := podmanTest.PodmanNoCache([]string{"image", "rm"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(125))
+ Expect(session).Should(Exit(125))
match, _ := session.ErrorGrepString("image name or ID must be specified")
Expect(match).To(BeTrue())
})
diff --git a/test/e2e/run_entrypoint_test.go b/test/e2e/run_entrypoint_test.go
index b1344a371..ebc06b36c 100644
--- a/test/e2e/run_entrypoint_test.go
+++ b/test/e2e/run_entrypoint_test.go
@@ -18,6 +18,7 @@ var _ = Describe("Podman run entrypoint", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/run_env_test.go b/test/e2e/run_env_test.go
new file mode 100644
index 000000000..867913a08
--- /dev/null
+++ b/test/e2e/run_env_test.go
@@ -0,0 +1,138 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman run", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.Setup()
+ podmanTest.SeedImages()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ processTestResult(f)
+
+ })
+
+ It("podman run environment test", func() {
+ session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOME"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ := session.GrepString("/root")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--user", "2", ALPINE, "printenv", "HOME"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("/sbin")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--env", "HOME=/foo", ALPINE, "printenv", "HOME"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("/foo")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR,BAZ", ALPINE, "printenv", "FOO"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("BAR,BAZ")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--env", "PATH=/bin", ALPINE, "printenv", "PATH"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("/bin")
+ Expect(match).Should(BeTrue())
+
+ os.Setenv("FOO", "BAR")
+ session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("BAR")
+ Expect(match).Should(BeTrue())
+ os.Unsetenv("FOO")
+
+ session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"})
+ session.WaitWithDefaultTimeout()
+ Expect(len(session.OutputToString())).To(Equal(0))
+ Expect(session.ExitCode()).To(Equal(1))
+
+ session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // This currently does not work
+ // Re-enable when hostname is an env variable
+ session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "printenv"})
+ session.Wait(10)
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("HOSTNAME")
+ Expect(match).Should(BeTrue())
+ })
+
+ It("podman run --host-env environment test", func() {
+ env := append(os.Environ(), "FOO=BAR")
+ session := podmanTest.PodmanAsUser([]string{"run", "--rm", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env)
+
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ := session.GrepString("BAR")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.PodmanAsUser([]string{"run", "--rm", "--env", "FOO=BAR1", "--env-host", ALPINE, "/bin/printenv", "FOO"}, 0, 0, "", env)
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("BAR1")
+ Expect(match).Should(BeTrue())
+ os.Unsetenv("FOO")
+ })
+
+ It("podman run --http-proxy test", func() {
+ os.Setenv("http_proxy", "1.2.3.4")
+ session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "http_proxy"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ := session.GrepString("1.2.3.4")
+ Expect(match).Should(BeTrue())
+
+ session = podmanTest.Podman([]string{"run", "--http-proxy=false", ALPINE, "printenv", "http_proxy"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(1))
+ Expect(session.OutputToString()).To(Equal(""))
+
+ session = podmanTest.Podman([]string{"run", "--env", "http_proxy=5.6.7.8", ALPINE, "printenv", "http_proxy"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("5.6.7.8")
+ Expect(match).Should(BeTrue())
+ os.Unsetenv("http_proxy")
+
+ session = podmanTest.Podman([]string{"run", "--http-proxy=false", "--env", "http_proxy=5.6.7.8", ALPINE, "printenv", "http_proxy"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ match, _ = session.GrepString("5.6.7.8")
+ Expect(match).Should(BeTrue())
+ os.Unsetenv("http_proxy")
+ })
+})
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 5be9db810..5946f3b7a 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -19,6 +19,7 @@ var _ = Describe("Podman run networking", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/run_test.go b/test/e2e/run_test.go
index 9b6de6f65..d94c6c169 100644
--- a/test/e2e/run_test.go
+++ b/test/e2e/run_test.go
@@ -193,79 +193,6 @@ var _ = Describe("Podman run", func() {
Expect(session.ExitCode()).To(Equal(0))
})
- It("podman run environment test", func() {
- session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "HOME"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ := session.GrepString("/root")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--rm", "--user", "2", ALPINE, "printenv", "HOME"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("/sbin")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "HOME=/foo", ALPINE, "printenv", "HOME"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("/foo")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR,BAZ", ALPINE, "printenv", "FOO"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("BAR,BAZ")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "PATH=/bin", ALPINE, "printenv", "PATH"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("/bin")
- Expect(match).Should(BeTrue())
-
- os.Setenv("FOO", "BAR")
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("BAR")
- Expect(match).Should(BeTrue())
- os.Unsetenv("FOO")
-
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO", ALPINE, "printenv", "FOO"})
- session.WaitWithDefaultTimeout()
- Expect(len(session.OutputToString())).To(Equal(0))
- Expect(session.ExitCode()).To(Equal(1))
-
- session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
-
- // This currently does not work
- // Re-enable when hostname is an env variable
- session = podmanTest.Podman([]string{"run", "--rm", ALPINE, "sh", "-c", "printenv"})
- session.Wait(10)
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("HOSTNAME")
- Expect(match).Should(BeTrue())
- })
-
- It("podman run --host-env environment test", func() {
- os.Setenv("FOO", "BAR")
- session := podmanTest.Podman([]string{"run", "--rm", "--env-host", ALPINE, "printenv", "FOO"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ := session.GrepString("BAR")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--rm", "--env", "FOO=BAR1", "--env-host", ALPINE, "printenv", "FOO"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ = session.GrepString("BAR1")
- Expect(match).Should(BeTrue())
- os.Unsetenv("FOO")
- })
-
It("podman run limits test", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"run", "--rm", "--ulimit", "rtprio=99", "--cap-add=sys_nice", fedoraMinimal, "cat", "/proc/self/sched"})
@@ -707,6 +634,7 @@ USER mail`
})
It("podman run --volumes-from flag with built-in volumes", func() {
+ Skip(v2fail)
session := podmanTest.Podman([]string{"create", redis, "sh"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -801,6 +729,7 @@ USER mail`
})
It("podman run --pod automatically", func() {
+ Skip(v2fail)
session := podmanTest.Podman([]string{"run", "--pod", "new:foobar", ALPINE, "ls"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
@@ -872,21 +801,6 @@ USER mail`
Expect(session).To(ExitWithError())
})
- It("podman run --http-proxy test", func() {
- os.Setenv("http_proxy", "1.2.3.4")
- session := podmanTest.Podman([]string{"run", "--rm", ALPINE, "printenv", "http_proxy"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- match, _ := session.GrepString("1.2.3.4")
- Expect(match).Should(BeTrue())
-
- session = podmanTest.Podman([]string{"run", "--http-proxy=false", ALPINE, "printenv", "http_proxy"})
- session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(1))
- Expect(session.OutputToString()).To(Equal(""))
- os.Unsetenv("http_proxy")
- })
-
It("podman run with restart-policy always restarts containers", func() {
testDir := filepath.Join(podmanTest.RunRoot, "restart-test")
diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go
index 41d61e9d9..83fdcabc9 100644
--- a/test/e2e/runlabel_test.go
+++ b/test/e2e/runlabel_test.go
@@ -31,6 +31,7 @@ var _ = Describe("podman container runlabel", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/save_test.go b/test/e2e/save_test.go
index 60825f975..aaa5ae180 100644
--- a/test/e2e/save_test.go
+++ b/test/e2e/save_test.go
@@ -116,4 +116,16 @@ var _ = Describe("Podman save", func() {
Expect(save).To(ExitWithError())
})
+ It("podman save image with digest reference", func() {
+ // pull a digest reference
+ session := podmanTest.PodmanNoCache([]string{"pull", ALPINELISTDIGEST})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ // save a digest reference should exit without error.
+ outfile := filepath.Join(podmanTest.TempDir, "temp.tar")
+ save := podmanTest.PodmanNoCache([]string{"save", "-o", outfile, ALPINELISTDIGEST})
+ save.WaitWithDefaultTimeout()
+ Expect(save.ExitCode()).To(Equal(0))
+ })
})
diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go
index 47b058845..5f6f5a8cf 100644
--- a/test/e2e/start_test.go
+++ b/test/e2e/start_test.go
@@ -17,6 +17,7 @@ var _ = Describe("Podman start", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/stats_test.go b/test/e2e/stats_test.go
index 762417a17..32f7cc520 100644
--- a/test/e2e/stats_test.go
+++ b/test/e2e/stats_test.go
@@ -21,6 +21,7 @@ var _ = Describe("Podman stats", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
cgroupsv2, err := cgroups.IsCgroup2UnifiedMode()
Expect(err).To(BeNil())
diff --git a/test/e2e/system_df_test.go b/test/e2e/system_df_test.go
index bbbdf30b0..5f261fcbf 100644
--- a/test/e2e/system_df_test.go
+++ b/test/e2e/system_df_test.go
@@ -20,6 +20,7 @@ var _ = Describe("podman system df", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/system_reset_test.go b/test/e2e/system_reset_test.go
index e5ce69739..f17747648 100644
--- a/test/e2e/system_reset_test.go
+++ b/test/e2e/system_reset_test.go
@@ -17,6 +17,7 @@ var _ = Describe("podman system reset", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/systemd_test.go b/test/e2e/systemd_test.go
index 9ec48ba00..c56fb00f2 100644
--- a/test/e2e/systemd_test.go
+++ b/test/e2e/systemd_test.go
@@ -23,6 +23,7 @@ var _ = Describe("Podman systemd", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
SkipIfRootless()
tempdir, err = CreateTempDirInTempDir()
if err != nil {
diff --git a/test/e2e/trust_test.go b/test/e2e/trust_test.go
index 8c97e6b28..2da370194 100644
--- a/test/e2e/trust_test.go
+++ b/test/e2e/trust_test.go
@@ -21,6 +21,7 @@ var _ = Describe("Podman trust", func() {
)
BeforeEach(func() {
+ Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
diff --git a/test/e2e/version_test.go b/test/e2e/version_test.go
index c2af613aa..e353b9f97 100644
--- a/test/e2e/version_test.go
+++ b/test/e2e/version_test.go
@@ -7,6 +7,7 @@ import (
"github.com/containers/libpod/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
+ . "github.com/onsi/gomega/gexec"
)
var _ = Describe("Podman version", func() {
@@ -35,53 +36,49 @@ var _ = Describe("Podman version", func() {
It("podman version", func() {
session := podmanTest.Podman([]string{"version"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- Expect(len(session.OutputToStringArray())).To(BeNumerically(">", 2))
- ok, _ := session.GrepString(version.Version)
- Expect(ok).To(BeTrue())
+ Expect(session).Should(Exit(0))
+ Expect(session.Out.Contents()).Should(ContainSubstring(version.Version))
})
It("podman -v", func() {
session := podmanTest.Podman([]string{"-v"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- ok, _ := session.GrepString(version.Version)
- Expect(ok).To(BeTrue())
+ Expect(session).Should(Exit(0))
+ Expect(session.Out.Contents()).Should(ContainSubstring(version.Version))
})
It("podman --version", func() {
session := podmanTest.Podman([]string{"--version"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
- ok, _ := session.GrepString(version.Version)
- Expect(ok).To(BeTrue())
+ Expect(session).Should(Exit(0))
+ Expect(session.Out.Contents()).Should(ContainSubstring(version.Version))
})
It("podman version --format json", func() {
session := podmanTest.Podman([]string{"version", "--format", "json"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
It("podman version --format json", func() {
session := podmanTest.Podman([]string{"version", "--format", "{{ json .}}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
It("podman version --format GO template", func() {
session := podmanTest.Podman([]string{"version", "--format", "{{ .Client.Version }}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"version", "--format", "{{ .Server.Version }}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{"version", "--format", "{{ .Version }}"})
session.WaitWithDefaultTimeout()
- Expect(session.ExitCode()).To(Equal(0))
+ Expect(session).Should(Exit(0))
})
})
diff --git a/test/e2e/volume_ls_test.go b/test/e2e/volume_ls_test.go
index da2d7ae77..7664e64bb 100644
--- a/test/e2e/volume_ls_test.go
+++ b/test/e2e/volume_ls_test.go
@@ -55,6 +55,7 @@ var _ = Describe("Podman volume ls", func() {
})
It("podman ls volume with Go template", func() {
+ Skip(v2fail)
session := podmanTest.Podman([]string{"volume", "create", "myvol"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/e2e/volume_prune_test.go b/test/e2e/volume_prune_test.go
index 3049646b0..b9ea90568 100644
--- a/test/e2e/volume_prune_test.go
+++ b/test/e2e/volume_prune_test.go
@@ -65,6 +65,7 @@ var _ = Describe("Podman volume prune", func() {
})
It("podman system prune --volume", func() {
+ Skip(v2fail)
session := podmanTest.Podman([]string{"volume", "create"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
diff --git a/test/system/150-login.bats b/test/system/150-login.bats
index e33217e14..e3e2b7aca 100644
--- a/test/system/150-login.bats
+++ b/test/system/150-login.bats
@@ -165,6 +165,7 @@ function setup() {
# Some push tests
@test "podman push fail" {
+
# Create an invalid authfile
authfile=${PODMAN_LOGIN_WORKDIR}/auth-$(random_string 10).json
rm -f $authfile
@@ -197,6 +198,7 @@ EOF
#
# https://github.com/containers/skopeo/issues/651
#
+
run_podman pull busybox
# Preserve its ID for later comparison against push/pulled image
diff --git a/test/system/250-generate-systemd.bats b/test/system/250-generate-systemd.bats
index 80199af5f..6155d6ace 100644
--- a/test/system/250-generate-systemd.bats
+++ b/test/system/250-generate-systemd.bats
@@ -10,6 +10,8 @@ SERVICE_NAME="podman_test_$(random_string)"
UNIT_DIR="$HOME/.config/systemd/user"
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"
+# FIXME: the must run as root (because of CI). It's also broken...
+
function setup() {
skip_if_not_systemd
skip_if_remote
diff --git a/test/utils/utils.go b/test/utils/utils.go
index 6ab8604a4..0131e023d 100644
--- a/test/utils/utils.go
+++ b/test/utils/utils.go
@@ -206,7 +206,7 @@ func WaitContainerReady(p PodmanTestCommon, id string, expStr string, timeout in
// OutputToString formats session output to string
func (s *PodmanSession) OutputToString() string {
- fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents()))
+ fields := strings.Fields(string(s.Out.Contents()))
return strings.Join(fields, " ")
}
diff --git a/vendor/github.com/containers/common/pkg/auth/auth.go b/vendor/github.com/containers/common/pkg/auth/auth.go
new file mode 100644
index 000000000..769e5a9fa
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/auth/auth.go
@@ -0,0 +1,182 @@
+package auth
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/pkg/docker/config"
+ "github.com/containers/image/v5/types"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh/terminal"
+)
+
+// GetDefaultAuthFile returns env value REGISTRY_AUTH_FILE as default --authfile path
+// used in multiple --authfile flag definitions
+func GetDefaultAuthFile() string {
+ return os.Getenv("REGISTRY_AUTH_FILE")
+}
+
+// CheckAuthFile validates filepath given by --authfile
+// used by command has --authfile flag
+func CheckAuthFile(authfile string) error {
+ if authfile == "" {
+ return nil
+ }
+ if _, err := os.Stat(authfile); err != nil {
+ return errors.Wrapf(err, "error checking authfile path %s", authfile)
+ }
+ return nil
+}
+
+// Login login to the server with creds from Stdin or CLI
+func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginOptions, registry string) error {
+ server := getRegistryName(registry)
+ authConfig, err := config.GetCredentials(systemContext, server)
+ if err != nil {
+ return errors.Wrapf(err, "error reading auth file")
+ }
+ if opts.GetLoginSet {
+ if authConfig.Username == "" {
+ return errors.Errorf("not logged into %s", server)
+ }
+ fmt.Fprintf(opts.Stdout, "%s\n", authConfig.Username)
+ return nil
+ }
+ if authConfig.IdentityToken != "" {
+ return errors.Errorf("currently logged in, auth file contains an Identity token")
+ }
+
+ password := opts.Password
+ if opts.StdinPassword {
+ var stdinPasswordStrBuilder strings.Builder
+ if opts.Password != "" {
+ return errors.Errorf("Can't specify both --password-stdin and --password")
+ }
+ if opts.Username == "" {
+ return errors.Errorf("Must provide --username with --password-stdin")
+ }
+ scanner := bufio.NewScanner(opts.Stdin)
+ for scanner.Scan() {
+ fmt.Fprint(&stdinPasswordStrBuilder, scanner.Text())
+ }
+ password = stdinPasswordStrBuilder.String()
+ }
+
+ // If no username and no password is specified, try to use existing ones.
+ if opts.Username == "" && password == "" && authConfig.Username != "" && authConfig.Password != "" {
+ fmt.Println("Authenticating with existing credentials...")
+ if err := docker.CheckAuth(ctx, systemContext, authConfig.Username, authConfig.Password, server); err == nil {
+ fmt.Fprintln(opts.Stdout, "Existing credentials are valid. Already logged in to", server)
+ return nil
+ }
+ fmt.Fprintln(opts.Stdout, "Existing credentials are invalid, please enter valid username and password")
+ }
+
+ username, password, err := getUserAndPass(opts, password, authConfig.Username)
+ if err != nil {
+ return errors.Wrapf(err, "error getting username and password")
+ }
+
+ if err = docker.CheckAuth(ctx, systemContext, username, password, server); err == nil {
+ // Write the new credentials to the authfile
+ if err = config.SetAuthentication(systemContext, server, username, password); err != nil {
+ return err
+ }
+ }
+ if err == nil {
+ fmt.Fprintln(opts.Stdout, "Login Succeeded!")
+ return nil
+ }
+ if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok {
+ logrus.Debugf("error logging into %q: %v", server, unauthorized)
+ return errors.Errorf("error logging into %q: invalid username/password", server)
+ }
+ return errors.Wrapf(err, "error authenticating creds for %q", server)
+}
+
+// getRegistryName scrubs and parses the input to get the server name
+func getRegistryName(server string) string {
+ // removes 'http://' or 'https://' from the front of the
+ // server/registry string if either is there. This will be mostly used
+ // for user input from 'Buildah login' and 'Buildah logout'.
+ server = strings.TrimPrefix(strings.TrimPrefix(server, "https://"), "http://")
+ // gets the registry from the input. If the input is of the form
+ // quay.io/myuser/myimage, it will parse it and just return quay.io
+ split := strings.Split(server, "/")
+ if len(split) > 1 {
+ return split[0]
+ }
+ return split[0]
+}
+
+// getUserAndPass gets the username and password from STDIN if not given
+// using the -u and -p flags. If the username prompt is left empty, the
+// displayed userFromAuthFile will be used instead.
+func getUserAndPass(opts *LoginOptions, password, userFromAuthFile string) (string, string, error) {
+ var err error
+ reader := bufio.NewReader(opts.Stdin)
+ username := opts.Username
+ if username == "" {
+ if userFromAuthFile != "" {
+ fmt.Fprintf(opts.Stdout, "Username (%s): ", userFromAuthFile)
+ } else {
+ fmt.Fprint(opts.Stdout, "Username: ")
+ }
+ username, err = reader.ReadString('\n')
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error reading username")
+ }
+ // If the user just hit enter, use the displayed user from the
+ // the authentication file. This allows to do a lazy
+ // `$ buildah login -p $NEW_PASSWORD` without specifying the
+ // user.
+ if strings.TrimSpace(username) == "" {
+ username = userFromAuthFile
+ }
+ }
+ if password == "" {
+ fmt.Fprint(opts.Stdout, "Password: ")
+ pass, err := terminal.ReadPassword(0)
+ if err != nil {
+ return "", "", errors.Wrapf(err, "error reading password")
+ }
+ password = string(pass)
+ fmt.Fprintln(opts.Stdout)
+ }
+ return strings.TrimSpace(username), password, err
+}
+
+// Logout removes the authentication of server from authfile
+// removes all authtication if specifies all in the options
+func Logout(systemContext *types.SystemContext, opts *LogoutOptions, server string) error {
+ if server != "" {
+ server = getRegistryName(server)
+ }
+ if err := CheckAuthFile(opts.AuthFile); err != nil {
+ return err
+ }
+
+ if opts.All {
+ if err := config.RemoveAllAuthentication(systemContext); err != nil {
+ return err
+ }
+ fmt.Fprintln(opts.Stdout, "Removed login credentials for all registries")
+ return nil
+ }
+
+ err := config.RemoveAuthentication(systemContext, server)
+ switch err {
+ case nil:
+ fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", server)
+ return nil
+ case config.ErrNotLoggedIn:
+ return errors.Errorf("Not logged into %s\n", server)
+ default:
+ return errors.Wrapf(err, "error logging out of %q", server)
+ }
+}
diff --git a/vendor/github.com/containers/common/pkg/auth/cli.go b/vendor/github.com/containers/common/pkg/auth/cli.go
new file mode 100644
index 000000000..dffd06718
--- /dev/null
+++ b/vendor/github.com/containers/common/pkg/auth/cli.go
@@ -0,0 +1,47 @@
+package auth
+
+import (
+ "io"
+
+ "github.com/spf13/pflag"
+)
+
+// LoginOptions represents common flags in login
+// caller should define bool or optionalBool fields for flags --get-login and --tls-verify
+type LoginOptions struct {
+ AuthFile string
+ CertDir string
+ GetLoginSet bool
+ Password string
+ Username string
+ StdinPassword bool
+ Stdin io.Reader
+ Stdout io.Writer
+}
+
+// LogoutOptions represents the results for flags in logout
+type LogoutOptions struct {
+ AuthFile string
+ All bool
+ Stdin io.Reader
+ Stdout io.Writer
+}
+
+// GetLoginFlags defines and returns login flags for containers tools
+func GetLoginFlags(flags *LoginOptions) *pflag.FlagSet {
+ fs := pflag.FlagSet{}
+ fs.StringVar(&flags.AuthFile, "authfile", GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
+ fs.StringVar(&flags.CertDir, "cert-dir", "", "use certificates at the specified path to access the registry")
+ fs.StringVarP(&flags.Password, "password", "p", "", "Password for registry")
+ fs.StringVarP(&flags.Username, "username", "u", "", "Username for registry")
+ fs.BoolVar(&flags.StdinPassword, "password-stdin", false, "Take the password from stdin")
+ return &fs
+}
+
+// GetLogoutFlags defines and returns logout flags for containers tools
+func GetLogoutFlags(flags *LogoutOptions) *pflag.FlagSet {
+ fs := pflag.FlagSet{}
+ fs.StringVar(&flags.AuthFile, "authfile", GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
+ fs.BoolVarP(&flags.All, "all", "a", false, "Remove the cached credentials for all registries in the auth file")
+ return &fs
+}
diff --git a/vendor/github.com/containers/common/pkg/config/default.go b/vendor/github.com/containers/common/pkg/config/default.go
index 8b87d3725..446382ac7 100644
--- a/vendor/github.com/containers/common/pkg/config/default.go
+++ b/vendor/github.com/containers/common/pkg/config/default.go
@@ -141,13 +141,18 @@ func DefaultConfig() (*Config, error) {
netns = "slirp4netns"
}
+ cgroupNS := "host"
+ if cgroup2, _ := cgroupv2.Enabled(); cgroup2 {
+ cgroupNS = "private"
+ }
+
return &Config{
Containers: ContainersConfig{
Devices: []string{},
Volumes: []string{},
Annotations: []string{},
ApparmorProfile: DefaultApparmorProfile,
- CgroupNS: "private",
+ CgroupNS: cgroupNS,
Cgroups: "enabled",
DefaultCapabilities: DefaultCapabilities,
DefaultSysctls: []string{},
@@ -173,7 +178,7 @@ func DefaultConfig() (*Config, error) {
SeccompProfile: SeccompDefaultPath,
ShmSize: DefaultShmSize,
UTSNS: "private",
- UserNS: "private",
+ UserNS: "host",
UserNSSize: DefaultUserNSSize,
},
Network: NetworkConfig{
diff --git a/vendor/github.com/containers/common/pkg/config/libpodConfig.go b/vendor/github.com/containers/common/pkg/config/libpodConfig.go
index cdb38a514..89566f789 100644
--- a/vendor/github.com/containers/common/pkg/config/libpodConfig.go
+++ b/vendor/github.com/containers/common/pkg/config/libpodConfig.go
@@ -224,6 +224,12 @@ func newLibpodConfig(c *Config) error {
}
}
+ // hard code EventsLogger to "file" to match older podman versions.
+ if config.EventsLogger != "file" {
+ logrus.Debugf("Ignoring lipod.conf EventsLogger setting %q. Use containers.conf if you want to change this setting and remove libpod.conf files.", config.EventsLogger)
+ config.EventsLogger = "file"
+ }
+
c.libpodToContainersConfig(config)
return nil
diff --git a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go
index 893bf1da9..8ffadd859 100644
--- a/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go
+++ b/vendor/github.com/rootless-containers/rootlesskit/pkg/port/builtin/parent/parent.go
@@ -2,11 +2,14 @@ package parent
import (
"context"
+ "fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
+ "strconv"
+ "strings"
"sync"
"syscall"
@@ -84,6 +87,39 @@ func (d *driver) RunParentDriver(initComplete chan struct{}, quit <-chan struct{
return nil
}
+func isEPERM(err error) bool {
+ k := "permission denied"
+ // As of Go 1.14, errors.Is(err, syscall.EPERM) does not seem to work for
+ // "listen tcp 0.0.0.0:80: bind: permission denied" error from net.ListenTCP().
+ return errors.Is(err, syscall.EPERM) || strings.Contains(err.Error(), k)
+}
+
+// annotateEPERM annotates origErr for human-readability
+func annotateEPERM(origErr error, spec port.Spec) error {
+ // Read "net.ipv4.ip_unprivileged_port_start" value (typically 1024)
+ // TODO: what for IPv6?
+ // NOTE: sync.Once should not be used here
+ b, e := ioutil.ReadFile("/proc/sys/net/ipv4/ip_unprivileged_port_start")
+ if e != nil {
+ return origErr
+ }
+ start, e := strconv.Atoi(strings.TrimSpace(string(b)))
+ if e != nil {
+ return origErr
+ }
+ if spec.ParentPort >= start {
+ // origErr is unrelated to ip_unprivileged_port_start
+ return origErr
+ }
+ text := fmt.Sprintf("cannot expose privileged port %d, you might need to add \"net.ipv4.ip_unprivileged_port_start=0\" (currently %d) to /etc/sysctl.conf", spec.ParentPort, start)
+ if filepath.Base(os.Args[0]) == "rootlesskit" {
+ // NOTE: The following sentence is appended only if Args[0] == "rootlesskit", because it does not apply to Podman (as of Podman v1.9).
+ // Podman launches the parent driver in the child user namespace (but in the parent network namespace), which disables the file capability.
+ text += ", or set CAP_NET_BIND_SERVICE on rootlesskit binary"
+ }
+ return errors.Wrap(origErr, text)
+}
+
func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, error) {
d.mu.Lock()
err := portutil.ValidatePortSpec(spec, d.ports)
@@ -106,6 +142,9 @@ func (d *driver) AddPort(ctx context.Context, spec port.Spec) (*port.Status, err
return nil, errors.New("spec was not validated?")
}
if err != nil {
+ if isEPERM(err) {
+ err = annotateEPERM(err, spec)
+ }
return nil, err
}
d.mu.Lock()
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 10635baf9..c1d803f84 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -82,8 +82,9 @@ github.com/containers/buildah/pkg/secrets
github.com/containers/buildah/pkg/supplemented
github.com/containers/buildah/pkg/umask
github.com/containers/buildah/util
-# github.com/containers/common v0.9.1
+# github.com/containers/common v0.9.5
github.com/containers/common/pkg/apparmor
+github.com/containers/common/pkg/auth
github.com/containers/common/pkg/capabilities
github.com/containers/common/pkg/cgroupv2
github.com/containers/common/pkg/config
@@ -453,7 +454,7 @@ github.com/prometheus/common/model
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
-# github.com/rootless-containers/rootlesskit v0.9.3
+# github.com/rootless-containers/rootlesskit v0.9.4
github.com/rootless-containers/rootlesskit/pkg/msgutil
github.com/rootless-containers/rootlesskit/pkg/port
github.com/rootless-containers/rootlesskit/pkg/port/builtin