summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.cirrus.yml119
-rwxr-xr-x.papr.sh18
-rw-r--r--.travis.yml39
-rw-r--r--Dockerfile12
-rw-r--r--README.md4
-rw-r--r--cmd/podman/checkpoint.go73
-rw-r--r--cmd/podman/container.go2
-rw-r--r--cmd/podman/restore.go73
-rw-r--r--cmd/podman/runlabel.go9
-rw-r--r--cmd/podman/shared/funcs.go5
-rw-r--r--cmd/podman/shared/funcs_test.go10
-rw-r--r--completions/bash/podman42
-rwxr-xr-xcontrib/cirrus/build_vm_images.sh59
-rwxr-xr-xcontrib/cirrus/integration_test.sh28
-rw-r--r--contrib/cirrus/lib.sh258
-rwxr-xr-xcontrib/cirrus/ooe.sh39
-rw-r--r--contrib/cirrus/packer/README.md2
-rw-r--r--contrib/cirrus/packer/centos_setup.sh69
-rw-r--r--contrib/cirrus/packer/fedora_setup.sh72
-rw-r--r--contrib/cirrus/packer/libpod_images.json124
-rw-r--r--contrib/cirrus/packer/rhel_setup.sh111
-rw-r--r--contrib/cirrus/packer/ubuntu_setup.sh93
-rwxr-xr-xcontrib/cirrus/setup_environment.sh77
-rwxr-xr-xcontrib/cirrus/unit_test.sh30
-rwxr-xr-xcontrib/cirrus/verify_source.sh30
-rwxr-xr-xdocker2
-rw-r--r--docs/podman-container-checkpoint.1.md30
-rw-r--r--docs/podman-container-restore.1.md37
-rw-r--r--docs/podman-container.1.md2
-rw-r--r--docs/tutorials/podman_tutorial.md22
-rw-r--r--libpod/container_api.go30
-rw-r--r--libpod/container_easyjson.go2
-rw-r--r--libpod/container_internal.go111
-rw-r--r--libpod/container_internal_linux.go158
-rw-r--r--libpod/container_internal_unsupported.go8
-rw-r--r--libpod/oci.go83
-rw-r--r--libpod/oci_linux.go6
-rw-r--r--libpod/oci_unsupported.go2
-rw-r--r--pkg/resolvconf/dns/resolvconf.go28
-rw-r--r--pkg/resolvconf/resolvconf.go242
-rw-r--r--pkg/spec/spec.go9
-rw-r--r--test/e2e/checkpoint_test.go129
-rw-r--r--test/e2e/libpod_suite_test.go37
43 files changed, 2197 insertions, 139 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
new file mode 100644
index 000000000..bc471cb84
--- /dev/null
+++ b/.cirrus.yml
@@ -0,0 +1,119 @@
+---
+
+# Only github users with write-access can define or use encrypted variables
+# This credential represents a service account with access to manage both VMs
+# and storage.
+gcp_credentials: ENCRYPTED[885c6e4297dd8d6f67593c42b810353af0c505a7a670e2c6fd830c56e86bbb2debcc3c18f942d0d46ab36b63521061d4]
+
+# Default VM to use for testing, unless values overriden by specific tasks (below)
+gce_instance:
+ image_project: "libpod-218412"
+ zone: "us-central1-a" # Required by Cirrus for the time being
+ cpu: 2
+ memory: "4Gb"
+ disk: 40
+
+# Main collection of env. varss to set for all scripts. All others
+# are cooked in by $SCRIPT_BASE/setup_environment.sh
+env:
+ CNI_COMMIT: "7480240de9749f9a0a5c8614b17f1f03e0c06ab9"
+ CRIO_COMMIT: "662dbb31b5d4f5ed54511a47cde7190c61c28677"
+ RUNC_COMMIT: "ad0f5255060d36872be04de22f8731f38ef2d7b1"
+ # File to update in home-dir with task-specific env. var values
+ ENVLIB: ".bash_profile"
+ # Overrides default location (/tmp/cirrus) for repo clone
+ CIRRUS_WORKING_DIR: "/go/src/github.com/containers/libpod"
+ # Required so $ENVLIB gets loaded
+ CIRRUS_SHELL: "/bin/bash"
+ # Save a little typing (path relative to $CIRRUS_WORKING_DIR)
+ SCRIPT_BASE: "./contrib/cirrus"
+ PACKER_BASE: "./contrib/cirrus/packer"
+
+# Every *_task runs in parallel in separate VMs. The name prefix only for reference
+# in WebUI, and will be followed by matrix details. This task does all the
+# per-pr unit/integration testing.
+full_vm_testing_task:
+
+ gce_instance:
+ # Generate multiple 'test' tasks, covering all possible
+ # 'matrix' combinations. All run in parallel.
+ matrix:
+ # Images are generated separetly, from build_images_task (below)
+ image_name: "ubuntu-1804-bionic-v20180911-libpod-5763563410948096"
+ # TODO: Make these work (also build_images_task below)
+ #image_name: "rhel-server-ec2-7-5-165-1-libpod-5358668723781632"
+ #image_name: "centos-7-v20180911-libpod-5358668723781632"
+ #image_name: "fedora-cloud-base-28-1-1-7-libpod-5358668723781632"
+
+ timeout_in: 120m
+
+ # Every *_script runs in sequence, for each task. The name prefix is for
+ # WebUI reference. The values may be strings...
+ setup_environment_script: $SCRIPT_BASE/setup_environment.sh
+
+ # ...or lists of strings
+ verify_source_script:
+ - whoami # root!
+ - $SCRIPT_BASE/verify_source.sh
+
+ unit_test_script: $SCRIPT_BASE/unit_test.sh
+
+ integration_test_script: $SCRIPT_BASE/integration_test.sh
+
+
+# This task build new images for future PR testing, but only after a PR merge.
+# These images save needing to install/setup the same environment to test every
+# PR. The 'active' image for testing is selected by the 'image_name' items in
+# task above. Currently this requires manually updating them, but this could
+# be automated (see comment at end).
+
+build_vm_images_task:
+ # Only produce new images after a PR merge
+ only_if: $CIRRUS_BRANCH == 'master'
+
+ # Require tests to pass first.
+ depends_on:
+ - test # i.e. 'test_task'
+
+ env:
+ # CSV of packer builder names to enable (see $PACKER_BASE/libpod_images.json)
+ PACKER_BUILDS: "ubuntu-18"
+ # TODO: Make these work (also full_vm_testing_task above)
+ # PACKER_BUILDS: "rhel-7,centos-7,fedora-28,ubuntu-18"
+ # Command to register a RHEL VM
+ RHSM_COMMAND: ENCRYPTED[fec01433222af1ed0b8e40e89e7d18f6ee2fa9f49a1e721dc72f7eed3c740661215d1bd05cb54ac66a1a62116b92bdce]
+ # Additional environment variables needed to build GCE images, within a GCE VM
+ SERVICE_ACCOUNT: ENCRYPTED[02e03838b1156eb9516c7cc1e888e287910759842275f3c7bc2b4d56075cc6740e29ffa0ab71ebdbbd079673361dd8c9]
+ GCE_SSH_USERNAME: ENCRYPTED[a19a4ec62423e3e0fe4e7d1a5c9f11eda8fde321b9047ab5ed5590c2b1d7a2d12091c2be1531f949eae927059c2ae531]
+ GCP_PROJECT_ID: ENCRYPTED[77cb2d392bbc8d17412547d7d91f8d190089bf6e6b96eab9927994bbff6ab2c691ba0329ac7a650ba6182fbbab9fb68d]
+ # Existing base values to use, output images get epoc stamped names
+ PACKER_VER: "1.3.1"
+ # low-level base VM image name inputs to packer
+ CENTOS_BASE_IMAGE: "centos-7-v20180911"
+ RHEL_BASE_IMAGE: "rhel-server-ec2-7-5-165-1"
+ FEDORA_BASE_IMAGE: "fedora-cloud-base-28-1-1-7"
+ UBUNTU_BASE_IMAGE: "ubuntu-1804-bionic-v20180911"
+
+ gce_instance:
+ image_name: "image-builder-image" # Simply CentOS 7 + packer dependencies
+ # Additional permissions for building GCE images, within a GCE VM
+ scopes:
+ - compute
+ - devstorage.full_control
+ # Doesn't need many local resources to run
+ cpu: 2
+ memory: "2Gb"
+ disk: 20
+ environment_script: $SCRIPT_BASE/setup_environment.sh
+ build_vm_images_script: $SCRIPT_BASE/build_vm_images.sh
+
+ # TODO,Continuous Delivery: Automaticly open a libpod PR after using 'sed' to replace
+ # the image_names with the new (just build) images. That will
+ # cause a new round of testing to happen (via the PR) using
+ # the new images. When all is good, the PR may be manually
+ # merged so all PR testing uses the new images. The script
+ # names (below) describe their purpose in this workflow.
+ # deploy_images_script:
+ # - clone_podman_release_branch.sh
+ # - modify_cirrus_yaml_image_names.sh
+ # - commit_and_create_upstream_pr.sh
diff --git a/.papr.sh b/.papr.sh
index 6155fe0df..120b3d94b 100755
--- a/.papr.sh
+++ b/.papr.sh
@@ -6,6 +6,18 @@ export PATH=$HOME/gopath/bin:$PATH:$GOPATH/bin
export GOSRC=$GOPATH/src/github.com/containers/libpod
DIST=${DIST:=""}
+CONTAINER_RUNTIME=${DIST:=""}
+
+source /etc/os-release
+
+INTEGRATION_TEST_ENVS=""
+
+# For all distributions not Fedora, we need to skip USERNS tests
+# for now.
+if [ "${ID}" != "fedora" ] || [ "${CONTAINER_RUNTIME}" != "" ]; then
+ INTEGRATION_TEST_ENVS="SKIP_USERNS=1"
+fi
+
pwd
# -i install
@@ -121,11 +133,11 @@ fi
# Run integration tests
if [ $integrationtest -eq 1 ]; then
make TAGS="${TAGS}" test-binaries
- SKIP_USERNS=1 make varlink_generate GOPATH=/go
+ make varlink_generate GOPATH=/go
if [ $runpython -eq 1 ]; then
- SKIP_USERNS=1 make clientintegration GOPATH=/go
+ make clientintegration GOPATH=/go
fi
- SKIP_USERNS=1 make ginkgo GOPATH=/go
+ make ginkgo GOPATH=/go $INTEGRATION_TEST_ENVS
fi
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 86744f728..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-language: go
-
-sudo: required
-dist: trusty
-
-services:
- - docker
-
-before_install:
- - if [ "${TRAVIS_OS_NAME}" = linux ]; then sudo apt-get -qq update; fi
- - if [ "${TRAVIS_OS_NAME}" = linux ]; then sudo apt-get -qq install btrfs-tools libdevmapper-dev libgpgme11-dev libapparmor-dev; fi
- - if [ "${TRAVIS_OS_NAME}" = linux ]; then sudo apt-get -qq install autoconf automake bison e2fslibs-dev libfuse-dev libtool liblzma-dev gettext; fi
- - if [ "${TRAVIS_OS_NAME}" = linux ]; then sudo apt-get -qq install python3-setuptools python3-dateutil python3-psutil; fi
- - if [ "${TRAVIS_OS_NAME}" = linux ]; then sudo make install.libseccomp.sudo; fi
-
-install:
- - make install.tools
-
-before_script:
- - export PATH=$HOME/gopath/bin:$PATH
- - export LD_LIBRARY_PATH=/usr/local/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
-
-env:
- global:
- - TRAVIS=1
-
-jobs:
- include:
- - stage: Build and Verify
- script:
- - make testunit
- go: 1.10.x
- - stage: Integration Test
- script:
- - make integration
- go: 1.9.x
-
-notifications:
- irc: "chat.freenode.net#podman"
diff --git a/Dockerfile b/Dockerfile
index 749c5edb9..2c43cb046 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,6 +18,8 @@ RUN apt-get update && apt-get install -y \
libaio-dev \
libcap-dev \
libfuse-dev \
+ libnet-dev \
+ libnl-3-dev \
libostree-dev \
libprotobuf-dev \
libprotobuf-c0-dev \
@@ -110,6 +112,16 @@ RUN set -x \
&& go get -u github.com/mailru/easyjson/... \
&& install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
+# Install criu
+ENV CRIU_COMMIT 584cbe4643c3fc7dc901ff08bf923ca0fe7326f9
+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/
diff --git a/README.md b/README.md
index 7f534f0ca..8bee9dc92 100644
--- a/README.md
+++ b/README.md
@@ -80,8 +80,8 @@ Information about contributing to this project.
Buildah and Podman are two complementary Open-source projects that are available on
most Linux platforms and both projects reside at [GitHub.com](https://github.com)
-with Buildah [here](https://github.com/containers/buildah) and
-Podman [here](https://github.com/containers/libpod). Both Buildah and Podman are
+with [Buildah](https://buildah.io) [(GitHub)](https://github.com/containers/buildah) and
+[Podman](https://podman.io) [(GitHub)](https://github.com/containers/libpod). Both Buildah and Podman are
command line tools that work on OCI images and containers. The two projects
differentiate in their specialization.
diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go
new file mode 100644
index 000000000..cbbbcd740
--- /dev/null
+++ b/cmd/podman/checkpoint.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var (
+ checkpointDescription = `
+ podman container checkpoint
+
+ Checkpoints one or more running containers. The container name or ID can be used.
+`
+ checkpointFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "keep, k",
+ Usage: "keep all temporary checkpoint files",
+ },
+ }
+ checkpointCommand = cli.Command{
+ Name: "checkpoint",
+ Usage: "Checkpoints one or more containers",
+ Description: checkpointDescription,
+ Flags: checkpointFlags,
+ Action: checkpointCmd,
+ ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]",
+ }
+)
+
+func checkpointCmd(c *cli.Context) error {
+ if rootless.IsRootless() {
+ return errors.New("checkpointing a container requires root")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ keep := c.Bool("keep")
+ args := c.Args()
+ if len(args) < 1 {
+ return errors.Errorf("you must provide at least one container name or id")
+ }
+
+ var lastError error
+ for _, arg := range args {
+ ctr, err := runtime.LookupContainer(arg)
+ if err != nil {
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "error looking up container %q", arg)
+ continue
+ }
+ if err = ctr.Checkpoint(context.TODO(), keep); err != nil {
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "failed to checkpoint container %v", ctr.ID())
+ } else {
+ fmt.Println(ctr.ID())
+ }
+ }
+ return lastError
+}
diff --git a/cmd/podman/container.go b/cmd/podman/container.go
index 82c1c824d..ff634278f 100644
--- a/cmd/podman/container.go
+++ b/cmd/podman/container.go
@@ -7,6 +7,7 @@ import (
var (
subCommands = []cli.Command{
attachCommand,
+ checkpointCommand,
cleanupCommand,
commitCommand,
createCommand,
@@ -23,6 +24,7 @@ var (
// pruneCommand,
refreshCommand,
restartCommand,
+ restoreCommand,
rmCommand,
runCommand,
runlabelCommand,
diff --git a/cmd/podman/restore.go b/cmd/podman/restore.go
new file mode 100644
index 000000000..43ef87ca2
--- /dev/null
+++ b/cmd/podman/restore.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+var (
+ restoreDescription = `
+ podman container restore
+
+ Restores a container from a checkpoint. The container name or ID can be used.
+`
+ restoreFlags = []cli.Flag{
+ cli.BoolFlag{
+ Name: "keep, k",
+ Usage: "keep all temporary checkpoint files",
+ },
+ }
+ restoreCommand = cli.Command{
+ Name: "restore",
+ Usage: "Restores one or more containers from a checkpoint",
+ Description: restoreDescription,
+ Flags: restoreFlags,
+ Action: restoreCmd,
+ ArgsUsage: "CONTAINER-NAME [CONTAINER-NAME ...]",
+ }
+)
+
+func restoreCmd(c *cli.Context) error {
+ if rootless.IsRootless() {
+ return errors.New("restoring a container requires root")
+ }
+
+ runtime, err := libpodruntime.GetRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "could not get runtime")
+ }
+ defer runtime.Shutdown(false)
+
+ keep := c.Bool("keep")
+ args := c.Args()
+ if len(args) < 1 {
+ return errors.Errorf("you must provide at least one container name or id")
+ }
+
+ var lastError error
+ for _, arg := range args {
+ ctr, err := runtime.LookupContainer(arg)
+ if err != nil {
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "error looking up container %q", arg)
+ continue
+ }
+ if err = ctr.Restore(context.TODO(), keep); err != nil {
+ if lastError != nil {
+ fmt.Fprintln(os.Stderr, lastError)
+ }
+ lastError = errors.Wrapf(err, "failed to restore container %v", ctr.ID())
+ } else {
+ fmt.Println(ctr.ID())
+ }
+ }
+ return lastError
+}
diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go
index c5dd98ee6..34e6b9093 100644
--- a/cmd/podman/runlabel.go
+++ b/cmd/podman/runlabel.go
@@ -94,6 +94,14 @@ func runlabelCmd(c *cli.Context) error {
newImage *image.Image
)
+ // Evil images could trick into recursively executing the runlabel
+ // command. Avoid this by setting the "PODMAN_RUNLABEL_NESTED" env
+ // variable when executing a label first.
+ nested := os.Getenv("PODMAN_RUNLABEL_NESTED")
+ if nested == "1" {
+ return fmt.Errorf("nested runlabel calls: runlabels cannot execute the runlabel command")
+ }
+
opts := make(map[string]string)
runtime, err := libpodruntime.GetRuntime(c)
if err != nil {
@@ -177,6 +185,7 @@ func runlabelCmd(c *cli.Context) error {
cmd := shared.GenerateCommand(runLabel, imageName, c.String("name"))
env := shared.GenerateRunEnvironment(c.String("name"), imageName, opts)
+ env = append(env, "PODMAN_RUNLABEL_NESTED=1")
if !c.Bool("quiet") {
fmt.Printf("Command: %s\n", strings.Join(cmd, " "))
diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go
index 5c401634c..21e7fe10d 100644
--- a/cmd/podman/shared/funcs.go
+++ b/cmd/podman/shared/funcs.go
@@ -15,9 +15,8 @@ func GenerateCommand(command, imageName, name string) []string {
name = imageName
}
cmd := strings.Split(command, " ")
- // Replace the first position of cmd with podman whether
- // it is docker, /usr/bin/docker, or podman
- newCommand = append(newCommand, "podman")
+ // Replace the first element of cmd with "/proc/self/exe"
+ newCommand = append(newCommand, "/proc/self/exe")
for _, arg := range cmd[1:] {
var newArg string
switch arg {
diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go
index 3d0ac005f..612be480b 100644
--- a/cmd/podman/shared/funcs_test.go
+++ b/cmd/podman/shared/funcs_test.go
@@ -15,35 +15,35 @@ var (
func TestGenerateCommand(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
- correctCommand := "podman run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
+ correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
newCommand := GenerateCommand(inputCommand, "foo", "bar")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandPath(t *testing.T) {
inputCommand := "/usr/bin/docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
- correctCommand := "podman run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
+ correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
newCommand := GenerateCommand(inputCommand, "foo", "bar")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoSetName(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
- correctCommand := "podman run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install"
+ correctCommand := "/proc/self/exe run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install"
newCommand := GenerateCommand(inputCommand, "foo", "")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoName(t *testing.T) {
inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install"
- correctCommand := "podman run -it -e IMAGE=foo foo echo install"
+ correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install"
newCommand := GenerateCommand(inputCommand, "foo", "")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandAlreadyPodman(t *testing.T) {
inputCommand := "podman run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
- correctCommand := "podman run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
+ correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
newCommand := GenerateCommand(inputCommand, "foo", "bar")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
diff --git a/completions/bash/podman b/completions/bash/podman
index f63bf4469..604a25f5d 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -87,6 +87,10 @@ __podman_complete_containers_all() {
__podman_complete_containers "$@" --all
}
+__podman_complete_containers_created() {
+ __podman_complete_containers "$@" --all --filter status=created
+}
+
__podman_complete_containers_running() {
__podman_complete_containers "$@" --filter status=running
}
@@ -710,6 +714,24 @@ _podman_container_attach() {
_podman_attach
}
+_podman_container_checkpoint() {
+ local options_with_args="
+ --help -h
+ "
+ local boolean_options="
+ --keep
+ -k
+ "
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_containers_running
+ ;;
+ esac
+}
+
_podman_container_commit() {
_podman_commit
}
@@ -770,6 +792,24 @@ _podman_container_restart() {
_podman_restart
}
+_podman_container_restore() {
+ local options_with_args="
+ --help -h
+ "
+ local boolean_options="
+ --keep
+ -k
+ "
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ __podman_complete_containers_created
+ ;;
+ esac
+}
+
_podman_container_rm() {
_podman_rm
}
@@ -817,6 +857,7 @@ _podman_container() {
"
subcommands="
attach
+ checkpoint
commit
create
diff
@@ -831,6 +872,7 @@ _podman_container() {
port
refresh
restart
+ restore
rm
run
start
diff --git a/contrib/cirrus/build_vm_images.sh b/contrib/cirrus/build_vm_images.sh
new file mode 100755
index 000000000..8538ee910
--- /dev/null
+++ b/contrib/cirrus/build_vm_images.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+set -e
+source $(dirname $0)/lib.sh
+
+req_env_var "
+CNI_COMMIT $CNI_COMMIT
+CRIO_COMMIT $CRIO_COMMIT
+RUNC_COMMIT $RUNC_COMMIT
+PACKER_BUILDS $PACKER_BUILDS
+CENTOS_BASE_IMAGE $CENTOS_BASE_IMAGE
+UBUNTU_BASE_IMAGE $UBUNTU_BASE_IMAGE
+FEDORA_BASE_IMAGE $FEDORA_BASE_IMAGE
+RHEL_BASE_IMAGE $RHEL_BASE_IMAGE
+RHSM_COMMAND $RHSM_COMMAND
+CIRRUS_BUILD_ID $CIRRUS_BUILD_ID
+SERVICE_ACCOUNT $SERVICE_ACCOUNT
+GCE_SSH_USERNAME $GCE_SSH_USERNAME
+GCP_PROJECT_ID $GCP_PROJECT_ID
+PACKER_VER $PACKER_VER
+SCRIPT_BASE $SCRIPT_BASE
+PACKER_BASE $PACKER_BASE
+"
+
+# TODO: Skip building images if $CIRRUS_BRANCH =~ "master" and
+# commit message of $CIRRUS_CHANGE_IN_REPO contains a magic word
+# produced by 'commit_and_create_upstream_pr.sh' script (see .cirrus.yml)
+
+show_env_vars
+
+# Everything here is running on the 'image-builder-image' GCE image
+# Assume basic dependencies are all met, but there could be a newer version
+# of the packer binary
+PACKER_FILENAME="packer_${PACKER_VER}_linux_amd64.zip"
+mkdir -p "$HOME/packer"
+cd "$HOME/packer"
+# image_builder_image has packer pre-installed, check if same version requested
+if ! [[ -r "$PACKER_FILENAME" ]]
+then
+ curl -L -O https://releases.hashicorp.com/packer/$PACKER_VER/$PACKER_FILENAME
+ curl -L https://releases.hashicorp.com/packer/${PACKER_VER}/packer_${PACKER_VER}_SHA256SUMS | \
+ grep 'linux_amd64' > ./sha256sums
+ sha256sum --check ./sha256sums
+ unzip -o $PACKER_FILENAME
+ ./packer --help &> /dev/null # verify exit(0)
+fi
+
+set -x
+
+cd "$GOSRC"
+# N/B: /usr/sbin/packer is a DIFFERENT tool, and will exit 0 given the args below :(
+TEMPLATE="./$PACKER_BASE/libpod_images.json"
+
+$HOME/packer/packer inspect "$TEMPLATE"
+
+#$HOME/packer/packer build -machine-readable "-only=$PACKER_BUILDS" "$TEMPLATE" | tee /tmp/packer_log.csv
+$HOME/packer/packer build "-only=$PACKER_BUILDS" "$TEMPLATE"
+
+# TODO: Report back to PR names of built images
diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh
new file mode 100755
index 000000000..226053724
--- /dev/null
+++ b/contrib/cirrus/integration_test.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+set -e
+source $(dirname $0)/lib.sh
+
+req_env_var "
+GOSRC $GOSRC
+OS_RELEASE_ID $OS_RELEASE_ID
+OS_RELEASE_VER $OS_RELEASE_VER
+"
+
+show_env_vars
+
+set -x
+cd "$GOSRC"
+case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in
+ ubuntu-18)
+ make install PREFIX=/usr ETCDIR=/etc "BUILDTAGS=$BUILDTAGS"
+ make test-binaries "BUILDTAGS=$BUILDTAGS"
+ SKIP_USERNS=1 make localintegration "BUILDTAGS=$BUILDTAGS"
+ ;;
+ fedora-28) ;& # Continue to the next item
+ centos-7) ;&
+ rhel-7)
+ stub 'integration testing not working on $OS_RELEASE_ID'
+ ;;
+ *) bad_os_id_ver ;;
+esac
diff --git a/contrib/cirrus/lib.sh b/contrib/cirrus/lib.sh
new file mode 100644
index 000000000..e69f1e040
--- /dev/null
+++ b/contrib/cirrus/lib.sh
@@ -0,0 +1,258 @@
+
+
+# Library of common, shared utility functions. This file is intended
+# to be sourced by other scripts, not called directly.
+
+# Under some contexts these values are not set, make sure they are.
+USER="$(whoami)"
+HOME="$(getent passwd $USER | cut -d : -f 6)"
+if ! [[ "$PATH" =~ "/usr/local/bin" ]]
+then
+ export PATH="$PATH:/usr/local/bin"
+fi
+
+# In ci/testing environment, ensure variables are always loaded
+if [[ -r "$HOME/$ENVLIB" ]] && [[ -n "$CI" ]]
+then
+ # Make sure this is always loaded
+ source "$HOME/$ENVLIB"
+fi
+
+# Pass in a line delimited list of, space delimited name/value pairs
+# exit non-zero with helpful error message if any value is empty
+req_env_var() {
+ echo "$1" | while read NAME VALUE
+ do
+ if [[ -n "$NAME" ]] && [[ -z "$VALUE" ]]
+ then
+ echo "Required env. var. \$$NAME is not set"
+ exit 9
+ fi
+ done
+}
+
+# Some env. vars may contain secrets. Display values for known "safe"
+# and useful variables.
+# ref: https://cirrus-ci.org/guide/writing-tasks/#environment-variables
+show_env_vars() {
+ echo "
+BUILDTAGS $BUILDTAGS
+CI $CI
+CIRRUS_CI $CIRRUS_CI
+CI_NODE_INDEX $CI_NODE_INDEX
+CI_NODE_TOTAL $CI_NODE_TOTAL
+CONTINUOUS_INTEGRATION $CONTINUOUS_INTEGRATION
+CIRRUS_BASE_BRANCH $CIRRUS_BASE_BRANCH
+CIRRUS_BASE_SHA $CIRRUS_BASE_SHA
+CIRRUS_BRANCH $CIRRUS_BRANCH
+CIRRUS_BUILD_ID $CIRRUS_BUILD_ID
+CIRRUS_CHANGE_IN_REPO $CIRRUS_CHANGE_IN_REPO
+CIRRUS_CHANGE_MESSAGE $CIRRUS_CHANGE_MESSAGE
+CIRRUS_CLONE_DEPTH $CIRRUS_CLONE_DEPTH
+CIRRUS_DEFAULT_BRANCH $CIRRUS_DEFAULT_BRANCH
+CIRRUS_PR $CIRRUS_PR
+CIRRUS_TAG $CIRRUS_TAG
+CIRRUS_OS $CIRRUS_OS
+OS $OS
+CIRRUS_TASK_NAME $CIRRUS_TASK_NAME
+CIRRUS_TASK_ID $CIRRUS_TASK_ID
+CIRRUS_REPO_NAME $CIRRUS_REPO_NAME
+CIRRUS_REPO_OWNER $CIRRUS_REPO_OWNER
+CIRRUS_REPO_FULL_NAME $CIRRUS_REPO_FULL_NAME
+CIRRUS_REPO_CLONE_URL $CIRRUS_REPO_CLONE_URL
+CIRRUS_SHELL $CIRRUS_SHELL
+CIRRUS_USER_COLLABORATOR $CIRRUS_USER_COLLABORATOR
+CIRRUS_USER_PERMISSION $CIRRUS_USER_PERMISSION
+CIRRUS_WORKING_DIR $CIRRUS_WORKING_DIR
+CIRRUS_HTTP_CACHE_HOST $CIRRUS_HTTP_CACHE_HOST
+$(go env)
+ " | while read NAME VALUE
+ do
+ [[ -z "$NAME" ]] || echo "export $NAME=\"$VALUE\""
+ done
+}
+
+# Return a GCE image-name compatible string representation of distribution name
+os_release_id() {
+ eval "$(egrep -m 1 '^ID=' /etc/os-release | tr -d \' | tr -d \")"
+ echo "$ID"
+}
+
+# Return a GCE image-name compatible string representation of distribution major version
+os_release_ver() {
+ eval "$(egrep -m 1 '^VERSION_ID=' /etc/os-release | tr -d \' | tr -d \")"
+ echo "$VERSION_ID" | cut -d '.' -f 1
+}
+
+bad_os_id_ver() {
+ echo "Unknown/Unsupported distro. $OS_RELEASE_ID and/or version $OS_RELEASE_VER for $ARGS"
+ exit 42
+}
+
+stub() {
+ echo "STUB: Pretending to do $1"
+}
+
+# Run sudo in directory with GOPATH set
+cdsudo() {
+ DIR="$1"
+ shift
+ CMD="cd $DIR && $@"
+ sudo --preserve-env=GOPATH --non-interactive bash -c "$CMD"
+}
+
+
+# Helper/wrapper script to only show stderr/stdout on non-zero exit
+install_ooe() {
+ req_env_var "SCRIPT_BASE $SCRIPT_BASE"
+ echo "Installing script to mask stdout/stderr unless non-zero exit."
+ sudo install -D -m 755 "/tmp/libpod/$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
+#!/bin/bash
+
+scl enable rh-git29 -- git $@
+EOF
+ sudo chmod 755 /usr/bin/git
+}
+
+install_cni_plugins() {
+ echo "Installing CNI Plugins from commit $CNI_COMMIT"
+ req_env_var "
+ GOPATH $GOPATH
+ CNI_COMMIT $CNI_COMMIT
+ "
+ DEST="$GOPATH/src/github.com/containernetworking/plugins"
+ rm -rf "$DEST"
+ ooe.sh git clone "https://github.com/containernetworking/plugins.git" "$DEST"
+ cd "$DEST"
+ ooe.sh git checkout -q "$CNI_COMMIT"
+ ooe.sh ./build.sh
+ sudo mkdir -p /usr/libexec/cni
+ sudo cp bin/* /usr/libexec/cni
+}
+
+install_runc(){
+ OS_RELEASE_ID=$(os_release_id)
+ echo "Installing RunC from commit $RUNC_COMMIT"
+ echo "Platform is $OS_RELEASE_ID"
+ req_env_var "
+ GOPATH $GOPATH
+ RUNC_COMMIT $RUNC_COMMIT
+ OS_RELEASE_ID $OS_RELEASE_ID
+ "
+ if [[ "$OS_RELEASE_ID" =~ "ubuntu" ]]; then
+ echo "Running make install.libseccomp.sudo for ubuntu"
+ if ! [[ -d "/tmp/libpod" ]]
+ then
+ echo "Expecting a copy of libpod repository in /tmp/libpod"
+ exit 5
+ fi
+ mkdir -p "$GOPATH/src/github.com/containers/"
+ # Symlinks don't work with Go
+ cp -a /tmp/libpod "$GOPATH/src/github.com/containers/"
+ cd "$GOPATH/src/github.com/containers/libpod"
+ ooe.sh sudo make install.libseccomp.sudo
+ fi
+ DEST="$GOPATH/src/github.com/opencontainers/runc"
+ rm -rf "$DEST"
+ ooe.sh git clone https://github.com/opencontainers/runc.git "$DEST"
+ cd "$DEST"
+ ooe.sh git fetch origin --tags
+ ooe.sh git checkout -q "$RUNC_COMMIT"
+ ooe.sh make static BUILDTAGS="seccomp selinux"
+ sudo install -m 755 runc /usr/bin/runc
+}
+
+install_buildah() {
+ echo "Installing buildah from latest upstream master"
+ req_env_var "GOPATH $GOPATH"
+ DEST="$GOPATH/src/github.com/containers/buildah"
+ rm -rf "$DEST"
+ ooe.sh git clone https://github.com/containers/buildah "$DEST"
+ cd "$DEST"
+ ooe.sh make
+ ooe.sh sudo make install
+}
+
+# Requires $GOPATH and $CRIO_COMMIT to be set
+install_conmon(){
+ echo "Installing conmon from commit $CRIO_COMMIT"
+ req_env_var "
+ GOPATH $GOPATH
+ CRIO_COMMIT $CRIO_COMMIT
+ "
+ DEST="$GOPATH/src/github.com/kubernetes-sigs/cri-o.git"
+ rm -rf "$DEST"
+ ooe.sh git clone https://github.com/kubernetes-sigs/cri-o.git "$DEST"
+ cd "$DEST"
+ ooe.sh git fetch origin --tags
+ ooe.sh git checkout -q "$CRIO_COMMIT"
+ ooe.sh make
+ sudo install -D -m 755 bin/conmon /usr/libexec/podman/conmon
+}
+
+# Runs in testing VM, not image building
+install_testing_dependencies() {
+ echo "Installing ginkgo, gomega, and easyjson into \$GOPATH=$GOPATH"
+ req_env_var "
+ GOPATH $GOPATH
+ GOSRC $GOSRC
+ "
+ cd "$GOSRC"
+ ooe.sh go get -u github.com/onsi/ginkgo/ginkgo
+ ooe.sh install -D -m 755 "$GOPATH"/bin/ginkgo /usr/bin/
+ ooe.sh go get github.com/onsi/gomega/...
+ ooe.sh go get -u github.com/mailru/easyjson/...
+ sudo install -D -m 755 "$GOPATH"/bin/easyjson /usr/bin/
+}
+
+install_packer_copied_files(){
+ # Install cni config, policy and registry config
+ sudo install -D -m 755 /tmp/libpod/cni/87-podman-bridge.conflist \
+ /etc/cni/net.d/87-podman-bridge.conflist
+ sudo install -D -m 755 /tmp/libpod/test/policy.json \
+ /etc/containers/policy.json
+ sudo install -D -m 755 /tmp/libpod/test/redhat_sigstore.yaml \
+ /etc/containers/registries.d/registry.access.redhat.com.yaml
+}
+
+install_varlink(){
+ echo "Installing varlink from the cheese-factory"
+ ooe.sh sudo -H pip3 install varlink
+}
+
+_finalize(){
+ echo "Removing leftover giblets from cloud-init"
+ cd /
+ sudo rm -rf /var/lib/cloud
+ sudo rm -rf /root/.ssh/*
+ sudo rm -rf /home/*
+}
+
+rh_finalize(){
+ # Allow root ssh-logins
+ if [[ -r /etc/cloud/cloud.cfg ]]
+ then
+ sudo sed -re 's/^disable_root:.*/disable_root: 0/g' -i /etc/cloud/cloud.cfg
+ fi
+ echo "Resetting to fresh-state for usage as cloud-image."
+ sudo $(type -P dnf || type -P yum) 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
+}
+
+ubuntu_finalize(){
+ echo "Resetting to fresh-state for usage as cloud-image."
+ sudo rm -rf /var/cache/apt
+ _finalize
+}
diff --git a/contrib/cirrus/ooe.sh b/contrib/cirrus/ooe.sh
new file mode 100755
index 000000000..d79e574b2
--- /dev/null
+++ b/contrib/cirrus/ooe.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# This script executes a command while logging all output to a temporary
+# file. If the command exits non-zero, then all output is sent to the console,
+# before returning the exit code. If the script itself fails, the exit code 121
+# is returned.
+
+set -eo pipefail
+
+SCRIPT_PATH="$0"
+
+badusage() {
+ echo "Incorrect usage: $(basename $SCRIPT_PATH) <command> [options]" > /dev/stderr
+ echo "ERROR: $1"
+ exit 121
+}
+
+COMMAND="$@"
+[[ -n "$COMMAND" ]] || badusage "No command specified"
+
+OUTPUT_TMPFILE="$(mktemp -p '' $(basename $0)_output_XXXX)"
+output_on_error() {
+ RET=$?
+ set +e
+ if [[ "$RET" -ne "0" ]]
+ then
+ echo "---------------------------"
+ cat "$OUTPUT_TMPFILE"
+ echo "[$(date --iso-8601=second)] <exit $RET> $COMMAND"
+ fi
+ rm -f "$OUTPUT_TMPFILE"
+}
+trap "output_on_error" EXIT
+
+"$@" 2>&1 | while IFS='' read LINE # Preserve leading/trailing whitespace
+do
+ # Every stdout and (copied) stderr line
+ echo "[$(date --iso-8601=second)] $LINE"
+done >> "$OUTPUT_TMPFILE"
diff --git a/contrib/cirrus/packer/README.md b/contrib/cirrus/packer/README.md
new file mode 100644
index 000000000..8ff6947e9
--- /dev/null
+++ b/contrib/cirrus/packer/README.md
@@ -0,0 +1,2 @@
+These are definitions and scripts consumed by packer to produce the
+various distribution images used for CI testing.
diff --git a/contrib/cirrus/packer/centos_setup.sh b/contrib/cirrus/packer/centos_setup.sh
new file mode 100644
index 000000000..2253d7b35
--- /dev/null
+++ b/contrib/cirrus/packer/centos_setup.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# This script is called by packer on the subject CentOS VM, to setup the podman
+# build/test environment. It's not intended to be used outside of this context.
+
+set -e
+
+# Load in library (copied by packer, before this script was run)
+source /tmp/libpod/$SCRIPT_BASE/lib.sh
+
+req_env_var "
+SCRIPT_BASE $SCRIPT_BASE
+CNI_COMMIT $CNI_COMMIT
+CRIO_COMMIT $CRIO_COMMIT
+"
+
+install_ooe
+
+export GOPATH="$(mktemp -d)"
+trap "sudo rm -rf $GOPATH" EXIT
+
+ooe.sh sudo yum -y update
+
+ooe.sh sudo yum -y install centos-release-scl epel-release
+
+ooe.sh sudo yum -y install \
+ atomic-registries \
+ btrfs-progs-devel \
+ bzip2 \
+ device-mapper-devel \
+ findutils \
+ glib2-devel \
+ glibc-static \
+ gnupg \
+ golang \
+ golang-github-cpuguy83-go-md2man \
+ golang-github-cpuguy83-go-md2man \
+ gpgme-devel \
+ iptables \
+ libassuan-devel \
+ libseccomp-devel \
+ libselinux-devel \
+ lsof \
+ make \
+ nmap-ncat \
+ ostree-devel \
+ python \
+ python3-dateutil \
+ python3-psutil \
+ python3-pytoml \
+ runc \
+ skopeo-containers \
+ unzip \
+ which \
+ xz
+
+install_scl_git
+
+install_cni_plugins
+
+install_buildah
+
+install_conmon
+
+install_packer_copied_files
+
+rh_finalize
+
+echo "SUCCESS!"
diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh
new file mode 100644
index 000000000..53709fbdd
--- /dev/null
+++ b/contrib/cirrus/packer/fedora_setup.sh
@@ -0,0 +1,72 @@
+#!/bin/bash
+
+# This script is called by packer on the subject fedora VM, to setup the podman
+# build/test environment. It's not intended to be used outside of this context.
+
+set -e
+
+# Load in library (copied by packer, before this script was run)
+source /tmp/libpod/$SCRIPT_BASE/lib.sh
+
+req_env_var "
+SCRIPT_BASE $SCRIPT_BASE
+CNI_COMMIT $CNI_COMMIT
+CRIO_COMMIT $CRIO_COMMIT
+RUNC_COMMIT $RUNC_COMMIT
+"
+
+install_ooe
+
+export GOPATH="$(mktemp -d)"
+trap "sudo rm -rf $GOPATH" EXIT
+
+# breaks networking on f28/29 in GCE
+# ooe.sh sudo dnf update -y
+
+ooe.sh sudo dnf install -y \
+ atomic-registries \
+ btrfs-progs-devel \
+ bzip2 \
+ conmon \
+ device-mapper-devel \
+ findutils \
+ git \
+ glib2-devel \
+ glibc-static \
+ gnupg \
+ golang \
+ golang-github-cpuguy83-go-md2man \
+ golang-github-cpuguy83-go-md2man \
+ gpgme-devel \
+ iptables \
+ libassuan-devel \
+ libseccomp-devel \
+ libselinux-devel \
+ lsof \
+ make \
+ nmap-ncat \
+ ostree-devel \
+ procps-ng \
+ python \
+ python3-dateutil \
+ python3-psutil \
+ python3-pytoml \
+ runc \
+ skopeo-containers \
+ slirp4netns \
+ which\
+ xz
+
+install_varlink
+
+install_cni_plugins
+
+install_buildah
+
+install_conmon
+
+install_packer_copied_files
+
+rh_finalize # N/B: Halts system!
+
+echo "SUCCESS!"
diff --git a/contrib/cirrus/packer/libpod_images.json b/contrib/cirrus/packer/libpod_images.json
new file mode 100644
index 000000000..82a41ca25
--- /dev/null
+++ b/contrib/cirrus/packer/libpod_images.json
@@ -0,0 +1,124 @@
+{
+ "variables": {
+ "CNI_COMMIT": "{{env `CNI_COMMIT`}}",
+ "CRIO_COMMIT": "{{env `CRIO_COMMIT`}}",
+ "RUNC_COMMIT": "{{env `RUNC_COMMIT`}}",
+
+ "CENTOS_BASE_IMAGE": "{{env `CENTOS_BASE_IMAGE`}}" ,
+ "UBUNTU_BASE_IMAGE": "{{env `UBUNTU_BASE_IMAGE`}}",
+ "FEDORA_BASE_IMAGE": "{{env `FEDORA_BASE_IMAGE`}}",
+ "RHEL_BASE_IMAGE": "{{env `RHEL_BASE_IMAGE`}}",
+
+ "GOSRC": "{{env `GOSRC`}}",
+ "PACKER_BASE": "{{env `PACKER_BASE`}}",
+ "SCRIPT_BASE": "{{env `SCRIPT_BASE`}}",
+
+ "SERVICE_ACCOUNT": "{{env `SERVICE_ACCOUNT`}}",
+ "GCP_PROJECT_ID": "{{env `GCP_PROJECT_ID`}}",
+ "CIRRUS_BUILD_ID": "{{env `CIRRUS_BUILD_ID`}}",
+ "GCE_SSH_USERNAME": "{{env `GCE_SSH_USERNAME`}}",
+ "RHSM_COMMAND": "{{env `RHSM_COMMAND`}}"
+ },
+ "sensitive-variables": [
+ "GCP_PROJECT_ID", "SERVICE_ACCOUNT", "GCE_SSH_USERNAME", "RHSM_COMMAND"
+ ],
+ "builders": [
+ {
+ "name": "rhel-7",
+ "type": "googlecompute",
+ "project_id": "{{user `GCP_PROJECT_ID`}}",
+ "zone": "us-central1-a",
+ "source_image": "{{user `RHEL_BASE_IMAGE`}}",
+ "image_name": "{{user `RHEL_BASE_IMAGE`}}-libpod-{{user `CIRRUS_BUILD_ID`}}",
+ "image_family": "{{user `RHEL_BASE_IMAGE`}}-libpod",
+ "service_account_email": "{{user `SERVICE_ACCOUNT`}}",
+ "communicator": "ssh",
+ "ssh_username": "ec2-user",
+ "ssh_pty": "true"
+ },{
+ "name": "centos-7",
+ "type": "googlecompute",
+ "project_id": "{{user `GCP_PROJECT_ID`}}",
+ "zone": "us-central1-a",
+ "source_image": "{{user `CENTOS_BASE_IMAGE`}}",
+ "image_name": "{{user `CENTOS_BASE_IMAGE`}}-libpod-{{user `CIRRUS_BUILD_ID`}}",
+ "image_family": "{{user `CENTOS_BASE_IMAGE`}}-libpod",
+ "service_account_email": "{{user `SERVICE_ACCOUNT`}}",
+ "communicator": "ssh",
+ "ssh_username": "{{user `GCE_SSH_USERNAME`}}",
+ "ssh_pty": "true"
+ },{
+ "name": "fedora-28",
+ "type": "googlecompute",
+ "project_id": "{{user `GCP_PROJECT_ID`}}",
+ "zone": "us-central1-a",
+ "source_image": "{{user `FEDORA_BASE_IMAGE`}}",
+ "image_name": "{{user `FEDORA_BASE_IMAGE`}}-libpod-{{user `CIRRUS_BUILD_ID`}}",
+ "image_family": "{{user `FEDORA_BASE_IMAGE`}}-libpod",
+ "service_account_email": "{{user `SERVICE_ACCOUNT`}}",
+ "communicator": "ssh",
+ "ssh_username": "fedora",
+ "ssh_pty": "true"
+ },{
+ "name": "ubuntu-18",
+ "type": "googlecompute",
+ "project_id": "{{user `GCP_PROJECT_ID`}}",
+ "zone": "us-central1-a",
+ "source_image": "{{user `UBUNTU_BASE_IMAGE`}}",
+ "image_name": "{{user `UBUNTU_BASE_IMAGE`}}-libpod-{{user `CIRRUS_BUILD_ID`}}",
+ "image_family": "{{user `UBUNTU_BASE_IMAGE`}}-libpod",
+ "service_account_email": "{{user `SERVICE_ACCOUNT`}}",
+ "communicator": "ssh",
+ "ssh_username": "{{user `GCE_SSH_USERNAME`}}",
+ "ssh_pty": "true"
+ }
+ ],
+ "provisioners": [
+ {
+ "type": "file",
+ "source": "{{user `GOSRC`}}",
+ "destination": "/tmp/libpod"
+ },{
+ "type": "shell",
+ "only": ["rhel-7"],
+ "script": "{{user `GOSRC`}}/{{user `PACKER_BASE`}}/rhel_setup.sh",
+ "environment_vars": [
+ "SCRIPT_BASE={{user `SCRIPT_BASE`}}",
+ "CNI_COMMIT={{user `CNI_COMMIT`}}",
+ "CRIO_COMMIT={{user `CRIO_COMMIT`}}",
+ "RUNC_COMMIT={{user `RUNC_COMMIT`}}",
+ "RHSM_COMMAND={{user `RHSM_COMMAND`}}"
+ ]
+ },{
+ "type": "shell",
+ "only": ["centos-7"],
+ "script": "{{user `GOSRC`}}/{{user `PACKER_BASE`}}/centos_setup.sh",
+ "environment_vars": [
+ "SCRIPT_BASE={{user `SCRIPT_BASE`}}",
+ "CNI_COMMIT={{user `CNI_COMMIT`}}",
+ "CRIO_COMMIT={{user `CRIO_COMMIT`}}",
+ "RUNC_COMMIT={{user `RUNC_COMMIT`}}"
+ ]
+ },{
+ "type": "shell",
+ "only": ["fedora-28"],
+ "script": "{{user `GOSRC`}}/{{user `PACKER_BASE`}}/fedora_setup.sh",
+ "environment_vars": [
+ "SCRIPT_BASE={{user `SCRIPT_BASE`}}",
+ "CNI_COMMIT={{user `CNI_COMMIT`}}",
+ "CRIO_COMMIT={{user `CRIO_COMMIT`}}",
+ "RUNC_COMMIT={{user `RUNC_COMMIT`}}"
+ ]
+ },{
+ "type": "shell",
+ "only": ["ubuntu-18"],
+ "script": "{{user `GOSRC`}}/{{user `PACKER_BASE`}}/ubuntu_setup.sh",
+ "environment_vars": [
+ "SCRIPT_BASE={{user `SCRIPT_BASE`}}",
+ "CNI_COMMIT={{user `CNI_COMMIT`}}",
+ "CRIO_COMMIT={{user `CRIO_COMMIT`}}",
+ "RUNC_COMMIT={{user `RUNC_COMMIT`}}"
+ ]
+ }
+ ]
+}
diff --git a/contrib/cirrus/packer/rhel_setup.sh b/contrib/cirrus/packer/rhel_setup.sh
new file mode 100644
index 000000000..b776a0d97
--- /dev/null
+++ b/contrib/cirrus/packer/rhel_setup.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+
+# This script is called by packer on the subject CentOS VM, to setup the podman
+# build/test environment. It's not intended to be used outside of this context.
+
+set -e
+
+# Load in library (copied by packer, before this script was run)
+source /tmp/libpod/$SCRIPT_BASE/lib.sh
+
+req_env_var "
+SCRIPT_BASE $SCRIPT_BASE
+CNI_COMMIT $CNI_COMMIT
+CRIO_COMMIT $CRIO_COMMIT
+RHSM_COMMAND $RHSM_COMMAND
+"
+
+install_ooe
+
+export GOPATH="$(mktemp -d)"
+export RHSMCMD="$(mktemp)"
+
+exit_handler() {
+ set +ex
+ cd /
+ sudo rm -rf "$RHSMCMD"
+ sudo rm -rf "$GOPATH"
+ sudo subscription-manager remove --all
+ sudo subscription-manager unregister
+ sudo subscription-manager clean
+}
+trap "exit_handler" EXIT
+
+# Avoid logging sensitive details
+echo "$RHSM_COMMAND" > "$RHSMCMD"
+ooe.sh sudo bash "$RHSMCMD"
+sudo rm -rf "$RHSMCMD"
+
+ooe.sh sudo yum -y erase "rh-amazon-rhui-client*"
+ooe.sh sudo subscription-manager repos "--disable=*"
+ooe.sh sudo subscription-manager repos \
+ --enable=rhel-7-server-rpms \
+ --enable=rhel-7-server-optional-rpms \
+ --enable=rhel-7-server-extras-rpms \
+ --enable=rhel-server-rhscl-7-rpms
+
+ooe.sh sudo yum -y update
+
+# Frequently needed
+ooe.sh sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
+
+# Required for google to manage ssh keys
+sudo tee -a /etc/yum.repos.d/google-cloud-sdk.repo << EOM
+[google-cloud-compute]
+name=google-cloud-compute
+baseurl=https://packages.cloud.google.com/yum/repos/google-cloud-compute-el7-x86_64
+enabled=1
+gpgcheck=1
+repo_gpgcheck=1
+gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg
+ https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
+EOM
+
+ooe.sh sudo yum -y install \
+ atomic-registries \
+ btrfs-progs-devel \
+ bzip2 \
+ device-mapper-devel \
+ findutils \
+ glib2-devel \
+ glibc-static \
+ gnupg \
+ golang \
+ golang-github-cpuguy83-go-md2man \
+ golang-github-cpuguy83-go-md2man \
+ google-compute-engine \
+ google-compute-engine-oslogin \
+ gpgme-devel \
+ iptables \
+ libassuan-devel \
+ libseccomp-devel \
+ libselinux-devel \
+ lsof \
+ make \
+ nmap-ncat \
+ ostree-devel \
+ python \
+ python34-dateutil \
+ python34-psutil \
+ python34-pytoml \
+ runc \
+ skopeo-containers \
+ unzip \
+ which \
+ xz
+
+install_scl_git
+
+install_cni_plugins
+
+install_buildah
+
+install_conmon
+
+install_packer_copied_files
+
+exit_handler # release subscription!
+
+rh_finalize
+
+echo "SUCCESS!"
diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh
new file mode 100644
index 000000000..96b3a573f
--- /dev/null
+++ b/contrib/cirrus/packer/ubuntu_setup.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+# This script is called by packer on the subject Ubuntu VM, to setup the podman
+# build/test environment. It's not intended to be used outside of this context.
+
+set -e
+
+# Load in library (copied by packer, before this script was run)
+source /tmp/libpod/$SCRIPT_BASE/lib.sh
+
+req_env_var "
+SCRIPT_BASE $SCRIPT_BASE
+CNI_COMMIT $CNI_COMMIT
+CRIO_COMMIT $CRIO_COMMIT
+RUNC_COMMIT $RUNC_COMMIT
+"
+
+install_ooe
+
+export GOPATH="$(mktemp -d)"
+trap "sudo rm -rf $GOPATH" EXIT
+
+ooe.sh sudo apt-get -qq update
+ooe.sh sudo apt-get -qq update # sometimes it needs to get it twice :S
+ooe.sh sudo apt-get -qq upgrade
+ooe.sh sudo apt-get -qq install --no-install-recommends \
+ apparmor \
+ autoconf \
+ automake \
+ bison \
+ btrfs-tools \
+ build-essential \
+ curl \
+ e2fslibs-dev \
+ gawk \
+ gettext \
+ golang \
+ go-md2man \
+ iptables \
+ libaio-dev \
+ libapparmor-dev \
+ libcap-dev \
+ libdevmapper-dev \
+ libdevmapper1.02.1 \
+ libfuse-dev \
+ libglib2.0-dev \
+ libgpgme11-dev \
+ liblzma-dev \
+ libostree-dev \
+ libprotobuf-c0-dev \
+ libprotobuf-dev \
+ libtool \
+ libtool \
+ libudev-dev \
+ lsof \
+ netcat \
+ pkg-config \
+ protobuf-c-compiler \
+ protobuf-compiler \
+ python-minimal \
+ python3-dateutil \
+ python3-pip \
+ python3-psutil \
+ python3-pytoml \
+ python3-setuptools \
+ socat \
+ unzip \
+ xz-utils
+
+echo "Fixing Ubuntu kernel not enabling swap accounting by 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
+
+install_runc
+
+install_conmon
+
+install_cni_plugins
+
+install_buildah
+
+install_packer_copied_files
+
+install_varlink
+
+sudo curl https://raw.githubusercontent.com/projectatomic/registries/master/registries.fedora\
+ -o /etc/containers/registries.conf
+
+ubuntu_finalize
+
+echo "SUCCESS!"
diff --git a/contrib/cirrus/setup_environment.sh b/contrib/cirrus/setup_environment.sh
new file mode 100755
index 000000000..2302f0e15
--- /dev/null
+++ b/contrib/cirrus/setup_environment.sh
@@ -0,0 +1,77 @@
+#!/bin/bash
+
+set -e
+source $(dirname $0)/lib.sh
+
+req_env_var "
+CI $CI
+USER $USER
+HOME $HOME
+ENVLIB $ENVLIB
+SCRIPT_BASE $SCRIPT_BASE
+CIRRUS_BUILD_ID $CIRRUS_BUILD_ID"
+
+[[ "$SHELL" =~ "bash" ]] || chsh -s /bin/bash
+
+cd "$CIRRUS_WORKING_DIR" # for clarity of initial conditions
+
+# Verify basic dependencies
+for depbin in go rsync unzip sha256sum curl make
+do
+ if ! type -P "$depbin" &> /dev/null
+ then
+ echo "ERROR: $depbin binary not found in $PATH"
+ exit 2
+ fi
+done
+
+# Setup env. vars common to all tasks/scripts/platforms and
+# ensure they return for every following script execution.
+MARK="# Added by $0, manual changes will be lost."
+touch "$HOME/$ENVLIB"
+if ! grep -q "$MARK" "$HOME/$ENVLIB"
+then
+ cp "$HOME/$ENVLIB" "$HOME/${ENVLIB}_original"
+ # N/B: Single-quote items evaluated every time, double-quotes only once (right now).
+ for envstr in \
+ "$MARK" \
+ "export HEAD=\"$CIRRUS_CHANGE_IN_REPO\"" \
+ "export TRAVIS=\"1\"" \
+ "export GOSRC=\"$CIRRUS_WORKING_DIR\"" \
+ "export OS_RELEASE_ID=\"$(os_release_id)\"" \
+ "export OS_RELEASE_VER=\"$(os_release_ver)\"" \
+ "export OS_REL_VER=\"${OS_RELEASE_ID}-${OS_RELEASE_VER}\"" \
+ "export GOPATH=\"/go\"" \
+ 'export PATH="$HOME/bin:$GOPATH/bin:/usr/local/bin:$PATH"' \
+ 'export LD_LIBRARY_PATH="/usr/local/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"'
+ do
+ # Make permanent in later shells, and set in current shell
+ X=$(echo "$envstr" | tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X"
+ done
+
+ # Some setup needs to vary between distros
+ case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in
+ ubuntu-18)
+ envstr='export BUILDTAGS="seccomp $($GOSRC/hack/btrfs_tag.sh) $($GOSRC/hack/btrfs_installed_tag.sh) $($GOSRC/hack/ostree_tag.sh) varlink exclude_graphdriver_devicemapper"'
+ ;;
+ fedora-28) ;& # Continue to the next item
+ centos-7) ;&
+ rhel-7)
+ envstr='unset BUILDTAGS' # Use default from Makefile
+ ;;
+ *) bad_os_id_ver ;;
+ esac
+ X=$(echo "$envstr" | tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X"
+
+ # Do the same for golang env. vars
+ go env | while read envline
+ do
+ X=$(echo "export $envline" | tee -a "$HOME/$ENVLIB") && eval "$X" && echo "$X"
+ done
+
+ cd "${GOSRC}/"
+ source "$SCRIPT_BASE/lib.sh"
+
+ # Only testing-VMs need deps installed
+ [[ -n "$PACKER_BUILDS" ]] || install_testing_dependencies # must exist in $GOPATH
+fi
diff --git a/contrib/cirrus/unit_test.sh b/contrib/cirrus/unit_test.sh
new file mode 100755
index 000000000..cacc23045
--- /dev/null
+++ b/contrib/cirrus/unit_test.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+source $(dirname $0)/lib.sh
+
+req_env_var "
+GOSRC $GOSRC
+OS_RELEASE_ID $OS_RELEASE_ID
+OS_RELEASE_VER $OS_RELEASE_VER
+"
+
+show_env_vars
+
+set -x
+cd "$GOSRC"
+case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in
+ ubuntu-18)
+ make localunit "BUILDTAGS=$BUILDTAGS"
+ make "BUILDTAGS=$BUILDTAGS"
+ ;;
+ fedora-28)
+ make localunit
+ make
+ ;;
+ centos-7) ;& # Continue to the next item
+ rhel-7)
+ stub 'unit testing not working on $OS_RELEASE_ID'
+ ;;
+ *) bad_os_id_ver ;;
+esac
diff --git a/contrib/cirrus/verify_source.sh b/contrib/cirrus/verify_source.sh
new file mode 100755
index 000000000..860bafc00
--- /dev/null
+++ b/contrib/cirrus/verify_source.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+set -e
+source $(dirname $0)/lib.sh
+
+req_env_var "
+OS_RELEASE_ID $OS_RELEASE_ID
+OS_RELEASE_VER $OS_RELEASE_VER
+"
+
+show_env_vars
+
+set -x
+cd "$GOSRC"
+
+case "${OS_RELEASE_ID}-${OS_RELEASE_VER}" in
+ ubuntu-18)
+ make install.tools "BUILDTAGS=$BUILDTAGS"
+ make validate "BUILDTAGS=$BUILDTAGS"
+ # make lint "BUILDTAGS=$BUILDTAGS"
+ ;;
+ fedora-28) ;&
+ centos-7) ;&
+ rhel-7)
+ make install.tools
+ make validate
+ # make lint
+ ;;
+ *) bad_os_id_ver ;;
+esac
diff --git a/docker b/docker
index a68a37ab3..56ffb56ac 100755
--- a/docker
+++ b/docker
@@ -1,4 +1,4 @@
#!/bin/sh
[ -f /etc/containers/nodocker ] || \
echo "Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg." >&2
-exec /usr/bin/podman $@
+exec /usr/bin/podman "$@"
diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md
new file mode 100644
index 000000000..4906e0e12
--- /dev/null
+++ b/docs/podman-container-checkpoint.1.md
@@ -0,0 +1,30 @@
+% podman-container-checkpoint(1)
+
+## NAME
+podman\-container\-checkpoint - Checkpoints one or more running containers
+
+## SYNOPSIS
+**podman container checkpoint** [*options*] *container* ...
+
+## DESCRIPTION
+Checkpoints all the processes in one or more containers. You may use container IDs or names as input.
+
+## OPTIONS
+**-k**, **--keep**
+
+Keep all temporary log and statistics files created by CRIU during checkpointing. These files
+are not deleted if checkpointing fails for further debugging. If checkpointing succeeds these
+files are theoretically not needed, but if these files are needed Podman can keep the files
+for further analysis.
+
+## EXAMPLE
+
+podman container checkpoint mywebserver
+
+podman container checkpoint 860a4b23
+
+## SEE ALSO
+podman(1), podman-container-restore(1)
+
+## HISTORY
+September 2018, Originally compiled by Adrian Reber <areber@redhat.com>
diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md
new file mode 100644
index 000000000..6360bccb0
--- /dev/null
+++ b/docs/podman-container-restore.1.md
@@ -0,0 +1,37 @@
+% podman-container-restore(1)
+
+## NAME
+podman\-container\-restore - Restores one or more running containers
+
+## SYNOPSIS
+**podman container restore** [*options*] *container* ...
+
+## DESCRIPTION
+Restores a container from a checkpoint. You may use container IDs or names as input.
+
+## OPTIONS
+**-k**, **--keep**
+
+Keep all temporary log and statistics files created by CRIU during
+checkpointing as well as restoring. These files are not deleted if restoring
+fails for further debugging. If restoring succeeds these files are
+theoretically not needed, but if these files are needed Podman can keep the
+files for further analysis. This includes the checkpoint directory with all
+files created during checkpointing. The size required by the checkpoint
+directory is roughly the same as the amount of memory required by the
+processes in the checkpointed container.
+
+Without the **-k**, **--keep** option the checkpoint will be consumed and cannot be used
+again.
+
+## EXAMPLE
+
+podman container restore mywebserver
+
+podman container restore 860a4b23
+
+## SEE ALSO
+podman(1), podman-container-checkpoint(1)
+
+## HISTORY
+September 2018, Originally compiled by Adrian Reber <areber@redhat.com>
diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md
index bbc325823..eac3343d5 100644
--- a/docs/podman-container.1.md
+++ b/docs/podman-container.1.md
@@ -14,6 +14,7 @@ The container command allows you to manage containers
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| attach | [podman-attach(1)](podman-attach.1.md) | Attach to a running container. |
+| checkpoint | [podman-container-checkpoint(1)](podman-container-checkpoint.1.md) | Checkpoints one or more containers. |
| cleanup | [podman-container-cleanup(1)](podman-container-cleanup.1.md) | Cleanup containers network and mountpoints. |
| commit | [podman-commit(1)](podman-commit.1.md) | Create new image based on the changed container. |
| create | [podman-create(1)](podman-create.1.md) | Create a new container. |
@@ -29,6 +30,7 @@ The container command allows you to manage containers
| port | [podman-port(1)](podman-port.1.md) | List port mappings for the container. |
| refresh | [podman-refresh(1)](podman-container-refresh.1.md) | Refresh the state of all containers |
| restart | [podman-restart(1)](podman-restart.1.md) | Restart one or more containers. |
+| restore | [podman-container-restore(1)](podman-container-restore.1.md) | Restores one or more containers from a checkpoint. |
| rm | [podman-rm(1)](podman-rm.1.md) | Remove one or more containers. |
| run | [podman-run(1)](podman-run.1.md) | Run a command in a container. |
| start | [podman-start(1)](podman-start.1.md) | Starts one or more containers. |
diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md
index a866b8eed..152d65a59 100644
--- a/docs/tutorials/podman_tutorial.md
+++ b/docs/tutorials/podman_tutorial.md
@@ -157,6 +157,28 @@ $ sudo podman top <container_id>
101 31889 31873 0 09:21 ? 00:00:00 nginx: worker process
```
+### Checkpointing the container
+Checkpointing a container stops the container while writing the state of all processes in the container to disk.
+With this a container can later be restored and continue running at exactly the same point in time as the
+checkpoint. This capability requires CRIU 3.11 or later installed on the system.
+To checkpoint the container use:
+```console
+$ sudo podman container checkpoint <container_id>
+```
+
+### Restoring the container
+Restoring a container is only possible for a previously checkpointed container. The restored container will
+continue to run at exactly the same point in time it was checkpointed.
+To restore the container use:
+```console
+$ sudo podman container restore <container_id>
+```
+
+After being restored, the container will answer requests again as it did before checkpointing.
+```console
+# curl http://<IP_address>:8080
+```
+
### Stopping the container
To stop the httpd container:
```console
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 192ccd347..93becb80d 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -832,3 +832,33 @@ func (c *Container) Refresh(ctx context.Context) error {
return nil
}
+
+// Checkpoint checkpoints a container
+func (c *Container) Checkpoint(ctx context.Context, keep bool) error {
+ logrus.Debugf("Trying to checkpoint container %s", c)
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if err := c.syncContainer(); err != nil {
+ return err
+ }
+ }
+
+ return c.checkpoint(ctx, keep)
+}
+
+// Restore restores a container
+func (c *Container) Restore(ctx context.Context, keep bool) (err error) {
+ logrus.Debugf("Trying to restore container %s", c)
+ if !c.batched {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if err := c.syncContainer(); err != nil {
+ return err
+ }
+ }
+
+ return c.restore(ctx, keep)
+}
diff --git a/libpod/container_easyjson.go b/libpod/container_easyjson.go
index 2d0481f3b..916118aec 100644
--- a/libpod/container_easyjson.go
+++ b/libpod/container_easyjson.go
@@ -1,3 +1,5 @@
+// +build seccomp ostree selinux varlink exclude_graphdriver_devicemapper
+
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
package libpod
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 033426817..77bba9e85 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -15,9 +15,9 @@ import (
"github.com/containers/libpod/pkg/chrootuser"
"github.com/containers/libpod/pkg/hooks"
"github.com/containers/libpod/pkg/hooks/exec"
+ "github.com/containers/libpod/pkg/resolvconf"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/secrets"
- "github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/chrootarchive"
@@ -129,6 +129,11 @@ func (c *Container) ControlSocketPath() string {
return filepath.Join(c.bundlePath(), "ctl")
}
+// CheckpointPath returns the path to the directory containing the checkpoint
+func (c *Container) CheckpointPath() string {
+ return filepath.Join(c.bundlePath(), "checkpoint")
+}
+
// AttachSocketPath retrieves the path of the container's attach socket
func (c *Container) AttachSocketPath() string {
return filepath.Join(c.runtime.ociRuntime.socketsDir, c.ID(), "attach")
@@ -523,7 +528,7 @@ func (c *Container) init(ctx context.Context) error {
}
// With the spec complete, do an OCI create
- if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent); err != nil {
+ if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, false); err != nil {
return err
}
@@ -1012,12 +1017,6 @@ func (c *Container) writeStringToRundir(destFile, output string) (string, error)
return filepath.Join(c.state.DestinationRunDir, destFile), nil
}
-type resolvConf struct {
- nameServers []string
- searchDomains []string
- options []string
-}
-
// generateResolvConf generates a containers resolv.conf
func (c *Container) generateResolvConf() (string, error) {
// Determine the endpoint for resolv.conf in case it is a symlink
@@ -1025,86 +1024,56 @@ func (c *Container) generateResolvConf() (string, error) {
if err != nil {
return "", err
}
- orig, err := ioutil.ReadFile(resolvPath)
+
+ contents, err := ioutil.ReadFile(resolvPath)
if err != nil {
return "", errors.Wrapf(err, "unable to read %s", resolvPath)
}
- if len(c.config.DNSServer) == 0 && len(c.config.DNSSearch) == 0 && len(c.config.DNSOption) == 0 {
- return c.writeStringToRundir("resolv.conf", fmt.Sprintf("%s", orig))
- }
- // Read and organize the hosts /etc/resolv.conf
- resolv := createResolv(string(orig[:]))
-
- // Populate the resolv struct with user's dns search domains
- if len(c.config.DNSSearch) > 0 {
- resolv.searchDomains = nil
- // The . character means the user doesnt want any search domains in the container
- if !util.StringInSlice(".", c.config.DNSSearch) {
- resolv.searchDomains = append(resolv.searchDomains, c.Config().DNSSearch...)
- }
+ // Process the file to remove localhost nameservers
+ // TODO: set ipv6 enable bool more sanely
+ resolv, err := resolvconf.FilterResolvDNS(contents, true)
+ if err != nil {
+ return "", errors.Wrapf(err, "error parsing host resolv.conf")
}
- // Populate the resolv struct with user's dns servers
+ // Make a new resolv.conf
+ nameservers := resolvconf.GetNameservers(resolv.Content)
if len(c.config.DNSServer) > 0 {
- resolv.nameServers = nil
- for _, i := range c.config.DNSServer {
- resolv.nameServers = append(resolv.nameServers, i.String())
+ // We store DNS servers as net.IP, so need to convert to string
+ nameservers = []string{}
+ for _, server := range c.config.DNSServer {
+ nameservers = append(nameservers, server.String())
}
}
- // Populate the resolve struct with the users dns options
+ search := resolvconf.GetSearchDomains(resolv.Content)
+ if len(c.config.DNSSearch) > 0 {
+ search = c.config.DNSSearch
+ }
+
+ options := resolvconf.GetOptions(resolv.Content)
if len(c.config.DNSOption) > 0 {
- resolv.options = nil
- resolv.options = append(resolv.options, c.Config().DNSOption...)
+ options = c.config.DNSOption
}
- return c.writeStringToRundir("resolv.conf", resolv.ToString())
-}
-// createResolv creates a resolv struct from an input string
-func createResolv(input string) resolvConf {
- var resolv resolvConf
- for _, line := range strings.Split(input, "\n") {
- if strings.HasPrefix(line, "search") {
- fields := strings.Fields(line)
- if len(fields) < 2 {
- logrus.Debugf("invalid resolv.conf line %s", line)
- continue
- }
- resolv.searchDomains = append(resolv.searchDomains, fields[1:]...)
- } else if strings.HasPrefix(line, "nameserver") {
- fields := strings.Fields(line)
- if len(fields) < 2 {
- logrus.Debugf("invalid resolv.conf line %s", line)
- continue
- }
- resolv.nameServers = append(resolv.nameServers, fields[1])
- } else if strings.HasPrefix(line, "options") {
- fields := strings.Fields(line)
- if len(fields) < 2 {
- logrus.Debugf("invalid resolv.conf line %s", line)
- continue
- }
- resolv.options = append(resolv.options, fields[1:]...)
- }
+ destPath := filepath.Join(c.state.RunDir, "resolv.conf")
+
+ if err := os.Remove(destPath); err != nil && !os.IsNotExist(err) {
+ return "", errors.Wrapf(err, "error removing resolv.conf for container %s", c.ID())
}
- return resolv
-}
-//ToString returns a resolv struct in the form of a resolv.conf
-func (r resolvConf) ToString() string {
- var result string
- // Populate the output string with search domains
- result += fmt.Sprintf("search %s\n", strings.Join(r.searchDomains, " "))
- // Populate the output string with name servers
- for _, i := range r.nameServers {
- result += fmt.Sprintf("nameserver %s\n", i)
+ // Build resolv.conf
+ if _, err = resolvconf.Build(destPath, nameservers, search, options); err != nil {
+ return "", errors.Wrapf(err, "error building resolv.conf for container %s")
}
- // Populate the output string with dns options
- for _, i := range r.options {
- result += fmt.Sprintf("options %s\n", i)
+
+ // Relabel resolv.conf for the container
+ if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil {
+ return "", err
}
- return result
+
+ return filepath.Join(c.state.DestinationRunDir, "resolv.conf"), nil
}
// generateHosts creates a containers hosts file
diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go
index b77beaf64..0353124dd 100644
--- a/libpod/container_internal_linux.go
+++ b/libpod/container_internal_linux.go
@@ -4,12 +4,18 @@ package libpod
import (
"context"
+ "encoding/json"
"fmt"
+ "io/ioutil"
+ "net"
+ "os"
"path"
+ "path/filepath"
"strings"
"syscall"
"time"
+ cnitypes "github.com/containernetworking/cni/pkg/types/current"
crioAnnotations "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/chrootuser"
"github.com/containers/libpod/pkg/rootless"
@@ -307,3 +313,155 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr
return nil
}
+
+func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) {
+
+ if c.state.State != ContainerStateRunning {
+ return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State)
+ }
+ if err := c.runtime.ociRuntime.checkpointContainer(c); err != nil {
+ return err
+ }
+
+ // Save network.status. This is needed to restore the container with
+ // the same IP. Currently limited to one IP address in a container
+ // with one interface.
+ formatJSON, err := json.MarshalIndent(c.state.NetworkStatus, "", " ")
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(filepath.Join(c.bundlePath(), "network.status"), formatJSON, 0644); err != nil {
+ return err
+ }
+
+ logrus.Debugf("Checkpointed container %s", c.ID())
+
+ c.state.State = ContainerStateStopped
+
+ // Cleanup Storage and Network
+ if err := c.cleanup(ctx); err != nil {
+ return err
+ }
+
+ if !keep {
+ // Remove log file
+ os.Remove(filepath.Join(c.bundlePath(), "dump.log"))
+ // Remove statistic file
+ os.Remove(filepath.Join(c.bundlePath(), "stats-dump"))
+ }
+
+ return c.save()
+}
+
+func (c *Container) restore(ctx context.Context, keep bool) (err error) {
+
+ if (c.state.State != ContainerStateConfigured) && (c.state.State != ContainerStateExited) {
+ return errors.Wrapf(ErrCtrStateInvalid, "container %s is running or paused, cannot restore", c.ID())
+ }
+
+ // Let's try to stat() CRIU's inventory file. If it does not exist, it makes
+ // no sense to try a restore. This is a minimal check if a checkpoint exist.
+ if _, err := os.Stat(filepath.Join(c.CheckpointPath(), "inventory.img")); os.IsNotExist(err) {
+ return errors.Wrapf(err, "A complete checkpoint for this container cannot be found, cannot restore")
+ }
+
+ // Read network configuration from checkpoint
+ // Currently only one interface with one IP is supported.
+ networkStatusFile, err := os.Open(filepath.Join(c.bundlePath(), "network.status"))
+ if err == nil {
+ // The file with the network.status does exist. Let's restore the
+ // container with the same IP address as during checkpointing.
+ defer networkStatusFile.Close()
+ var networkStatus []*cnitypes.Result
+ networkJSON, err := ioutil.ReadAll(networkStatusFile)
+ if err != nil {
+ return err
+ }
+ json.Unmarshal(networkJSON, &networkStatus)
+ // Take the first IP address
+ var IP net.IP
+ if len(networkStatus) > 0 {
+ if len(networkStatus[0].IPs) > 0 {
+ IP = networkStatus[0].IPs[0].Address.IP
+ }
+ }
+ if IP != nil {
+ env := fmt.Sprintf("IP=%s", IP)
+ // Tell CNI which IP address we want.
+ os.Setenv("CNI_ARGS", env)
+ logrus.Debugf("Restoring container with %s", env)
+ }
+ }
+
+ if err := c.prepare(); err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ if err2 := c.cleanup(ctx); err2 != nil {
+ logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2)
+ }
+ }
+ }()
+
+ // TODO: use existing way to request static IPs, once it is merged in ocicni
+ // https://github.com/cri-o/ocicni/pull/23/
+
+ // CNI_ARGS was used to request a certain IP address. Unconditionally remove it.
+ os.Unsetenv("CNI_ARGS")
+
+ // Read config
+ jsonPath := filepath.Join(c.bundlePath(), "config.json")
+ logrus.Debugf("generate.NewFromFile at %v", jsonPath)
+ g, err := generate.NewFromFile(jsonPath)
+ if err != nil {
+ logrus.Debugf("generate.NewFromFile failed with %v", err)
+ return err
+ }
+
+ // We want to have the same network namespace as before.
+ if c.config.CreateNetNS {
+ g.AddOrReplaceLinuxNamespace(spec.NetworkNamespace, c.state.NetNS.Path())
+ }
+
+ // Save the OCI spec to disk
+ if err := c.saveSpec(g.Spec()); err != nil {
+ return err
+ }
+
+ if err := c.makeBindMounts(); err != nil {
+ return err
+ }
+
+ // Cleanup for a working restore.
+ c.removeConmonFiles()
+
+ if err := c.runtime.ociRuntime.createContainer(c, c.config.CgroupParent, true); err != nil {
+ return err
+ }
+
+ logrus.Debugf("Restored container %s", c.ID())
+
+ c.state.State = ContainerStateRunning
+
+ if !keep {
+ // Delete all checkpoint related files. At this point, in theory, all files
+ // should exist. Still ignoring errors for now as the container should be
+ // restored and running. Not erroring out just because some cleanup operation
+ // failed. Starting with the checkpoint directory
+ err = os.RemoveAll(c.CheckpointPath())
+ if err != nil {
+ logrus.Debugf("Non-fatal: removal of checkpoint directory (%s) failed: %v", c.CheckpointPath(), err)
+ }
+ cleanup := [...]string{"restore.log", "dump.log", "stats-dump", "stats-restore", "network.status"}
+ for _, delete := range cleanup {
+ file := filepath.Join(c.bundlePath(), delete)
+ err = os.Remove(file)
+ if err != nil {
+ logrus.Debugf("Non-fatal: removal of checkpoint file (%s) failed: %v", file, err)
+ }
+ }
+ }
+
+ return c.save()
+}
diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go
index 45b54efab..eed0449a9 100644
--- a/libpod/container_internal_unsupported.go
+++ b/libpod/container_internal_unsupported.go
@@ -27,3 +27,11 @@ func (c *Container) cleanupNetwork() error {
func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) {
return nil, ErrNotImplemented
}
+
+func (c *Container) checkpoint(ctx context.Context, keep bool) error {
+ return ErrNotImplemented
+}
+
+func (c *Container) restore(ctx context.Context, keep bool) error {
+ return ErrNotImplemented
+}
diff --git a/libpod/oci.go b/libpod/oci.go
index e5db06540..f6d320017 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -227,7 +227,7 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
return files, nil
}
-func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (err error) {
+func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
var stderrBuf bytes.Buffer
runtimeDir, err := GetRootlessRuntimeDir()
@@ -289,6 +289,10 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (er
args = append(args, "--syslog")
}
+ if restoreContainer {
+ args = append(args, "--restore", ctr.CheckpointPath())
+ }
+
logrus.WithFields(logrus.Fields{
"args": args,
}).Debugf("running conmon: %s", r.conmonPath)
@@ -452,9 +456,20 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
cmd := exec.Command(r.path, "state", ctr.ID())
cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
-
- out, err := cmd.CombinedOutput()
+ outPipe, err := cmd.StdoutPipe()
+ if err != nil {
+ return errors.Wrapf(err, "getting stdout pipe")
+ }
+ errPipe, err := cmd.StderrPipe()
if err != nil {
+ return errors.Wrapf(err, "getting stderr pipe")
+ }
+
+ if err := cmd.Start(); err != nil {
+ out, err2 := ioutil.ReadAll(errPipe)
+ if err2 != nil {
+ return errors.Wrapf(err, "error getting container %s state", ctr.ID())
+ }
if strings.Contains(string(out), "does not exist") {
ctr.removeConmonFiles()
ctr.state.State = ContainerStateExited
@@ -462,6 +477,12 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
}
return errors.Wrapf(err, "error getting container %s state. stderr/out: %s", ctr.ID(), out)
}
+
+ errPipe.Close()
+ out, err := ioutil.ReadAll(outPipe)
+ if err != nil {
+ return errors.Wrapf(err, "error reading stdout: %s", ctr.ID())
+ }
if err := json.NewDecoder(bytes.NewBuffer(out)).Decode(state); err != nil {
return errors.Wrapf(err, "error decoding container status for container %s", ctr.ID())
}
@@ -535,7 +556,12 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container) error {
// Sets time the container was started, but does not save it.
func (r *OCIRuntime) startContainer(ctr *Container) error {
// TODO: streams should probably *not* be our STDIN/OUT/ERR - redirect to buffers?
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "start", ctr.ID()); err != nil {
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "start", ctr.ID()); err != nil {
return err
}
@@ -547,7 +573,12 @@ func (r *OCIRuntime) startContainer(ctr *Container) error {
// killContainer sends the given signal to the given container
func (r *OCIRuntime) killContainer(ctr *Container, signal uint) error {
logrus.Debugf("Sending signal %d to container %s", signal, ctr.ID())
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "kill", ctr.ID(), fmt.Sprintf("%d", signal)); err != nil {
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", ctr.ID(), fmt.Sprintf("%d", signal)); err != nil {
return errors.Wrapf(err, "error sending signal to container %s", ctr.ID())
}
@@ -605,7 +636,12 @@ func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error {
args = []string{"kill", "--all", ctr.ID(), "KILL"}
}
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...); err != nil {
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, args...); err != nil {
// Again, check if the container is gone. If it is, exit cleanly.
err := unix.Kill(ctr.state.PID, 0)
if err == unix.ESRCH {
@@ -631,12 +667,22 @@ func (r *OCIRuntime) deleteContainer(ctr *Container) error {
// pauseContainer pauses the given container
func (r *OCIRuntime) pauseContainer(ctr *Container) error {
- return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "pause", ctr.ID())
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "pause", ctr.ID())
}
// unpauseContainer unpauses the given container
func (r *OCIRuntime) unpauseContainer(ctr *Container) error {
- return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "resume", ctr.ID())
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
+ return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "resume", ctr.ID())
}
// execContainer executes a command in a running container
@@ -734,13 +780,18 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
if len(execSessions) == 0 {
return nil
}
+ runtimeDir, err := GetRootlessRuntimeDir()
+ if err != nil {
+ return err
+ }
+ env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)}
// If timeout is 0, just use SIGKILL
if timeout > 0 {
// Stop using SIGTERM by default
// Use SIGSTOP after a timeout
logrus.Debugf("Killing all processes in container %s with SIGTERM", ctr.ID())
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "kill", "--all", ctr.ID(), "TERM"); err != nil {
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "TERM"); err != nil {
return errors.Wrapf(err, "error sending SIGTERM to container %s processes", ctr.ID())
}
@@ -755,7 +806,7 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
// Send SIGKILL
logrus.Debugf("Killing all processes in container %s with SIGKILL", ctr.ID())
- if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "kill", "--all", ctr.ID(), "KILL"); err != nil {
+ if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "kill", "--all", ctr.ID(), "KILL"); err != nil {
return errors.Wrapf(err, "error sending SIGKILL to container %s processes", ctr.ID())
}
@@ -766,3 +817,15 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error {
return nil
}
+
+// checkpointContainer checkpoints the given container
+func (r *OCIRuntime) checkpointContainer(ctr *Container) error {
+ // imagePath is used by CRIU to store the actual checkpoint files
+ imagePath := ctr.CheckpointPath()
+ // workPath will be used to store dump.log and stats-dump
+ workPath := ctr.bundlePath()
+ logrus.Debugf("Writing checkpoint to %s", imagePath)
+ logrus.Debugf("Writing checkpoint logs to %s", workPath)
+ return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "checkpoint",
+ "--image-path", imagePath, "--work-path", workPath, ctr.ID())
+}
diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go
index 210ba57d1..0447670b3 100644
--- a/libpod/oci_linux.go
+++ b/libpod/oci_linux.go
@@ -63,10 +63,10 @@ func newPipe() (parent *os.File, child *os.File, err error) {
// CreateContainer creates a container in the OCI runtime
// TODO terminal support for container
// Presently just ignoring conmon opts related to it
-func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) (err error) {
+func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
if ctr.state.UserNSRoot == "" {
// no need of an intermediate mount ns
- return r.createOCIContainer(ctr, cgroupParent)
+ return r.createOCIContainer(ctr, cgroupParent, restoreContainer)
}
var wg sync.WaitGroup
wg.Add(1)
@@ -103,7 +103,7 @@ func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) (err e
if err != nil {
return
}
- err = r.createOCIContainer(ctr, cgroupParent)
+ err = r.createOCIContainer(ctr, cgroupParent, restoreContainer)
}()
wg.Wait()
diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go
index 8cb4994d3..b133eb402 100644
--- a/libpod/oci_unsupported.go
+++ b/libpod/oci_unsupported.go
@@ -15,7 +15,7 @@ func newPipe() (parent *os.File, child *os.File, err error) {
return nil, nil, ErrNotImplemented
}
-func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string) (err error) {
+func (r *OCIRuntime) createContainer(ctr *Container, cgroupParent string, restoreContainer bool) (err error) {
return ErrNotImplemented
}
diff --git a/pkg/resolvconf/dns/resolvconf.go b/pkg/resolvconf/dns/resolvconf.go
new file mode 100644
index 000000000..cb4bd1033
--- /dev/null
+++ b/pkg/resolvconf/dns/resolvconf.go
@@ -0,0 +1,28 @@
+// Originally from github.com/docker/libnetwork/resolvconf/dns
+
+package dns
+
+import (
+ "regexp"
+)
+
+// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
+const IPLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
+
+// IPv4Localhost is a regex pattern for IPv4 localhost address range.
+const IPv4Localhost = `(127\.([0-9]{1,3}\.){2}[0-9]{1,3})`
+
+var localhostIPRegexp = regexp.MustCompile(IPLocalhost)
+var localhostIPv4Regexp = regexp.MustCompile(IPv4Localhost)
+
+// IsLocalhost returns true if ip matches the localhost IP regular expression.
+// Used for determining if nameserver settings are being passed which are
+// localhost addresses
+func IsLocalhost(ip string) bool {
+ return localhostIPRegexp.MatchString(ip)
+}
+
+// IsIPv4Localhost returns true if ip matches the IPv4 localhost regular expression.
+func IsIPv4Localhost(ip string) bool {
+ return localhostIPv4Regexp.MatchString(ip)
+}
diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go
new file mode 100644
index 000000000..fccd60093
--- /dev/null
+++ b/pkg/resolvconf/resolvconf.go
@@ -0,0 +1,242 @@
+// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf.
+// Originally from github.com/docker/libnetwork/resolvconf.
+package resolvconf
+
+import (
+ "bytes"
+ "io/ioutil"
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/containers/libpod/pkg/resolvconf/dns"
+ "github.com/docker/docker/pkg/ioutils"
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ // DefaultResolvConf points to the default file used for dns configuration on a linux machine
+ DefaultResolvConf = "/etc/resolv.conf"
+)
+
+var (
+ // Note: the default IPv4 & IPv6 resolvers are set to Google's Public DNS
+ defaultIPv4Dns = []string{"nameserver 8.8.8.8", "nameserver 8.8.4.4"}
+ defaultIPv6Dns = []string{"nameserver 2001:4860:4860::8888", "nameserver 2001:4860:4860::8844"}
+ ipv4NumBlock = `(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)`
+ ipv4Address = `(` + ipv4NumBlock + `\.){3}` + ipv4NumBlock
+ // This is not an IPv6 address verifier as it will accept a super-set of IPv6, and also
+ // will *not match* IPv4-Embedded IPv6 Addresses (RFC6052), but that and other variants
+ // -- e.g. other link-local types -- either won't work in containers or are unnecessary.
+ // For readability and sufficiency for Docker purposes this seemed more reasonable than a
+ // 1000+ character regexp with exact and complete IPv6 validation
+ ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})(%\w+)?`
+
+ localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + dns.IPLocalhost + `\s*\n*`)
+ nsIPv6Regexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
+ nsRegexp = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+ searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+ optionsRegexp = regexp.MustCompile(`^\s*options\s*(([^\s]+\s*)*)$`)
+)
+
+var lastModified struct {
+ sync.Mutex
+ sha256 string
+ contents []byte
+}
+
+// File contains the resolv.conf content and its hash
+type File struct {
+ Content []byte
+ Hash string
+}
+
+// Get returns the contents of /etc/resolv.conf and its hash
+func Get() (*File, error) {
+ return GetSpecific(DefaultResolvConf)
+}
+
+// GetSpecific returns the contents of the user specified resolv.conf file and its hash
+func GetSpecific(path string) (*File, error) {
+ resolv, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ hash, err := ioutils.HashData(bytes.NewReader(resolv))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: resolv, Hash: hash}, nil
+}
+
+// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash
+// and, if modified since last check, returns the bytes and new hash.
+// This feature is used by the resolv.conf updater for containers
+func GetIfChanged() (*File, error) {
+ lastModified.Lock()
+ defer lastModified.Unlock()
+
+ resolv, err := ioutil.ReadFile("/etc/resolv.conf")
+ if err != nil {
+ return nil, err
+ }
+ newHash, err := ioutils.HashData(bytes.NewReader(resolv))
+ if err != nil {
+ return nil, err
+ }
+ if lastModified.sha256 != newHash {
+ lastModified.sha256 = newHash
+ lastModified.contents = resolv
+ return &File{Content: resolv, Hash: newHash}, nil
+ }
+ // nothing changed, so return no data
+ return nil, nil
+}
+
+// GetLastModified retrieves the last used contents and hash of the host resolv.conf.
+// Used by containers updating on restart
+func GetLastModified() *File {
+ lastModified.Lock()
+ defer lastModified.Unlock()
+
+ return &File{Content: lastModified.contents, Hash: lastModified.sha256}
+}
+
+// FilterResolvDNS cleans up the config in resolvConf. It has two main jobs:
+// 1. It looks for localhost (127.*|::1) entries in the provided
+// resolv.conf, removing local nameserver entries, and, if the resulting
+// cleaned config has no defined nameservers left, adds default DNS entries
+// 2. Given the caller provides the enable/disable state of IPv6, the filter
+// code will remove all IPv6 nameservers if it is not enabled for containers
+//
+func FilterResolvDNS(resolvConf []byte, ipv6Enabled bool) (*File, error) {
+ cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
+ // if IPv6 is not enabled, also clean out any IPv6 address nameserver
+ if !ipv6Enabled {
+ cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
+ }
+ // if the resulting resolvConf has no more nameservers defined, add appropriate
+ // default DNS servers for IPv4 and (optionally) IPv6
+ if len(GetNameservers(cleanedResolvConf)) == 0 {
+ logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers: %v", defaultIPv4Dns)
+ dns := defaultIPv4Dns
+ if ipv6Enabled {
+ logrus.Infof("IPv6 enabled; Adding default IPv6 external servers: %v", defaultIPv6Dns)
+ dns = append(dns, defaultIPv6Dns...)
+ }
+ cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...)
+ }
+ hash, err := ioutils.HashData(bytes.NewReader(cleanedResolvConf))
+ if err != nil {
+ return nil, err
+ }
+ return &File{Content: cleanedResolvConf, Hash: hash}, nil
+}
+
+// getLines parses input into lines and strips away comments.
+func getLines(input []byte, commentMarker []byte) [][]byte {
+ lines := bytes.Split(input, []byte("\n"))
+ var output [][]byte
+ for _, currentLine := range lines {
+ var commentIndex = bytes.Index(currentLine, commentMarker)
+ if commentIndex == -1 {
+ output = append(output, currentLine)
+ } else {
+ output = append(output, currentLine[:commentIndex])
+ }
+ }
+ return output
+}
+
+// GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
+func GetNameservers(resolvConf []byte) []string {
+ nameservers := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ ns := nsRegexp.FindSubmatch(line)
+ if len(ns) > 0 {
+ nameservers = append(nameservers, string(ns[1]))
+ }
+ }
+ return nameservers
+}
+
+// GetNameserversAsCIDR returns nameservers (if any) listed in
+// /etc/resolv.conf as CIDR blocks (e.g., "1.2.3.4/32")
+// This function's output is intended for net.ParseCIDR
+func GetNameserversAsCIDR(resolvConf []byte) []string {
+ nameservers := []string{}
+ for _, nameserver := range GetNameservers(resolvConf) {
+ var address string
+ // If IPv6, strip zone if present
+ if strings.Contains(nameserver, ":") {
+ address = strings.Split(nameserver, "%")[0] + "/128"
+ } else {
+ address = nameserver + "/32"
+ }
+ nameservers = append(nameservers, address)
+ }
+ return nameservers
+}
+
+// GetSearchDomains returns search domains (if any) listed in /etc/resolv.conf
+// If more than one search line is encountered, only the contents of the last
+// one is returned.
+func GetSearchDomains(resolvConf []byte) []string {
+ domains := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ match := searchRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ domains = strings.Fields(string(match[1]))
+ }
+ return domains
+}
+
+// GetOptions returns options (if any) listed in /etc/resolv.conf
+// If more than one options line is encountered, only the contents of the last
+// one is returned.
+func GetOptions(resolvConf []byte) []string {
+ options := []string{}
+ for _, line := range getLines(resolvConf, []byte("#")) {
+ match := optionsRegexp.FindSubmatch(line)
+ if match == nil {
+ continue
+ }
+ options = strings.Fields(string(match[1]))
+ }
+ return options
+}
+
+// Build writes a configuration file to path containing a "nameserver" entry
+// for every element in dns, a "search" entry for every element in
+// dnsSearch, and an "options" entry for every element in dnsOptions.
+func Build(path string, dns, dnsSearch, dnsOptions []string) (*File, error) {
+ content := bytes.NewBuffer(nil)
+ if len(dnsSearch) > 0 {
+ if searchString := strings.Join(dnsSearch, " "); strings.Trim(searchString, " ") != "." {
+ if _, err := content.WriteString("search " + searchString + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ }
+ for _, dns := range dns {
+ if _, err := content.WriteString("nameserver " + dns + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ if len(dnsOptions) > 0 {
+ if optsString := strings.Join(dnsOptions, " "); strings.Trim(optsString, " ") != "" {
+ if _, err := content.WriteString("options " + optsString + "\n"); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ hash, err := ioutils.HashData(bytes.NewReader(content.Bytes()))
+ if err != nil {
+ return nil, err
+ }
+
+ return &File{Content: content.Bytes(), Hash: hash}, ioutil.WriteFile(path, content.Bytes(), 0644)
+}
diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go
index 1ad55fc8c..4c855d659 100644
--- a/pkg/spec/spec.go
+++ b/pkg/spec/spec.go
@@ -417,7 +417,7 @@ func setupSystemd(config *CreateConfig, g *generate.Generator) error {
return err
}
options := []string{"rw", "rprivate", "noexec", "nosuid", "nodev"}
- for _, dest := range []string{"/run", "/run/lock", "/sys/fs/cgroup/systemd"} {
+ for _, dest := range []string{"/run", "/run/lock"} {
if libpod.MountExists(mounts, dest) {
continue
}
@@ -441,6 +441,13 @@ func setupSystemd(config *CreateConfig, g *generate.Generator) error {
}
g.AddMount(tmpfsMnt)
}
+ tmpfsMnt := spec.Mount{
+ Destination: "/sys/fs/cgroup/systemd",
+ Type: "tmpfs",
+ Source: "tmpfs",
+ Options: append(options, "size=65536k"),
+ }
+ g.AddMount(tmpfsMnt)
return nil
}
diff --git a/test/e2e/checkpoint_test.go b/test/e2e/checkpoint_test.go
new file mode 100644
index 000000000..6c5d891a0
--- /dev/null
+++ b/test/e2e/checkpoint_test.go
@@ -0,0 +1,129 @@
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman checkpoint", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest PodmanTest
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ // At least CRIU 3.11 is needed
+ skip, err := podmanTest.isCriuAtLeast(31100)
+ if err != nil || skip {
+ Skip("CRIU missing or too old.")
+ }
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+ })
+
+ It("podman checkpoint bogus container", func() {
+ session := podmanTest.Podman([]string{"container", "checkpoint", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman restore bogus container", func() {
+ session := podmanTest.Podman([]string{"container", "restore", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman checkpoint a running container by id", func() {
+ // CRIU does not work with seccomp correctly on RHEL7
+ session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"container", "checkpoint", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ result = podmanTest.Podman([]string{"container", "restore", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+ })
+
+ It("podman checkpoint a running container by name", func() {
+ session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "--name", "test_name", "-d", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"container", "checkpoint", "test_name"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ result = podmanTest.Podman([]string{"container", "restore", "test_name"})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Up"))
+ })
+
+ It("podman pause a checkpointed container by id", func() {
+ session := podmanTest.Podman([]string{"run", "-it", "--security-opt", "seccomp=unconfined", "-d", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ cid := session.OutputToString()
+
+ result := podmanTest.Podman([]string{"container", "checkpoint", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ result = podmanTest.Podman([]string{"pause", cid})
+ result.WaitWithDefaultTimeout()
+
+ Expect(result.ExitCode()).To(Equal(125))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+ Expect(podmanTest.GetContainerStatus()).To(ContainSubstring("Exited"))
+
+ result = podmanTest.Podman([]string{"container", "restore", cid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+
+ result = podmanTest.Podman([]string{"rm", cid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(125))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(1))
+
+ result = podmanTest.Podman([]string{"rm", "-f", cid})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(podmanTest.NumberOfContainersRunning()).To(Equal(0))
+
+ })
+})
diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go
index d521632d7..a032b0e88 100644
--- a/test/e2e/libpod_suite_test.go
+++ b/test/e2e/libpod_suite_test.go
@@ -2,6 +2,7 @@ package integration
import (
"bufio"
+ "bytes"
"context"
"encoding/json"
"fmt"
@@ -64,6 +65,7 @@ type PodmanTest struct {
TempDir string
CgroupManager string
Host HostOS
+ CriuBinary string
}
// HostOS is a simple struct for the test os
@@ -164,6 +166,7 @@ func PodmanCreate(tempDir string) PodmanTest {
runCBinary = "/usr/bin/runc"
}
+ criuBinary := "/usr/sbin/criu"
CNIConfigDir := "/etc/cni/net.d"
p := PodmanTest{
@@ -179,6 +182,7 @@ func PodmanCreate(tempDir string) PodmanTest {
TempDir: tempDir,
CgroupManager: cgroupManager,
Host: host,
+ CriuBinary: criuBinary,
}
// Setup registries.conf ENV variable
@@ -678,6 +682,39 @@ func (p *PodmanTest) setRegistriesConfigEnv(b []byte) {
ioutil.WriteFile(outfile, b, 0644)
}
+func (p *PodmanTest) isCriuAtLeast(version int) (bool, error) {
+ cmd := exec.Command(p.CriuBinary, "-V")
+ var out bytes.Buffer
+ cmd.Stdout = &out
+ err := cmd.Run()
+ if err != nil {
+ return false, err
+ }
+
+ var x int
+ var y int
+ var z int
+
+ fmt.Sscanf(out.String(), "Version: %d.%d.%d", &x, &y, &z)
+
+ if strings.Contains(out.String(), "GitID") {
+ // If CRIU is built from git it contains a git ID.
+ // If that is the case, increase minor by one as this
+ // could mean we are running a development version.
+ y = y + 1
+ }
+
+ parsed_version := x*10000 + y*100 + z
+
+ fmt.Println(parsed_version)
+
+ if parsed_version >= version {
+ return false, nil
+ } else {
+ return true, nil
+ }
+}
+
func resetRegistriesConfigEnv() {
os.Setenv("REGISTRIES_CONFIG_PATH", "")
}