summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--cmd/podman/push.go5
-rw-r--r--contrib/systemd/system/podman-docker.conf1
-rw-r--r--docs/source/markdown/podman-build.1.md6
-rw-r--r--docs/source/markdown/podman-container-exists.1.md4
-rw-r--r--docs/source/markdown/podman-exec.1.md12
-rw-r--r--docs/source/markdown/podman-history.1.md12
-rw-r--r--docs/source/markdown/podman-image-exists.1.md4
-rw-r--r--docs/source/markdown/podman-remote.1.md20
-rw-r--r--docs/source/markdown/podman-rm.1.md33
-rw-r--r--docs/source/markdown/podman-rmi.1.md19
-rw-r--r--docs/source/markdown/podman-run.1.md8
-rw-r--r--docs/source/markdown/podman-stop.1.md20
-rw-r--r--docs/source/markdown/podman-system-prune.1.md6
-rw-r--r--docs/source/markdown/podman.1.md24
-rw-r--r--docs/source/network.rst2
-rw-r--r--go.mod4
-rw-r--r--go.sum5
-rw-r--r--install.md12
-rw-r--r--libpod/image/image.go9
-rw-r--r--pkg/api/handlers/generic/ping.go4
-rw-r--r--pkg/api/handlers/utils/errors.go11
-rw-r--r--pkg/api/handlers/version.go (renamed from pkg/api/handlers/generic/version.go)5
-rw-r--r--pkg/api/server/register_images.go23
-rw-r--r--pkg/api/server/register_version.go6
-rw-r--r--pkg/apparmor/apparmor_linux_template.go6
-rw-r--r--pkg/bindings/connection.go180
-rw-r--r--pkg/bindings/containers.go139
-rw-r--r--pkg/bindings/containers/containers.go255
-rw-r--r--pkg/bindings/containers/healthcheck.go26
-rw-r--r--pkg/bindings/containers/mount.go53
-rw-r--r--pkg/bindings/errors.go11
-rw-r--r--pkg/bindings/generate.go4
-rw-r--r--pkg/bindings/generate/generate.go4
-rw-r--r--pkg/bindings/healthcheck.go19
-rw-r--r--pkg/bindings/images.go111
-rw-r--r--pkg/bindings/images/images.go187
-rw-r--r--pkg/bindings/images/search.go40
-rw-r--r--pkg/bindings/mount.go26
-rw-r--r--pkg/bindings/network.go37
-rw-r--r--pkg/bindings/network/network.go50
-rw-r--r--pkg/bindings/play.go3
-rw-r--r--pkg/bindings/play/play.go7
-rw-r--r--pkg/bindings/pods.go129
-rw-r--r--pkg/bindings/pods/pods.go196
-rw-r--r--pkg/bindings/search.go39
-rw-r--r--pkg/bindings/test/common_test.go112
-rw-r--r--pkg/bindings/test/images_test.go92
-rw-r--r--pkg/bindings/volumes.go60
-rw-r--r--pkg/bindings/volumes/volumes.go85
-rw-r--r--pkg/inspect/inspect.go46
-rw-r--r--rootless.md1
-rw-r--r--test/apiv2/00-TEMPLATE6
-rw-r--r--test/apiv2/01-basic.at49
-rw-r--r--test/apiv2/10-images.at36
-rw-r--r--test/apiv2/20-containers.at29
-rw-r--r--test/apiv2/30-volumes.at14
-rw-r--r--test/apiv2/40-pods.at33
-rw-r--r--test/apiv2/README.md63
-rwxr-xr-xtest/apiv2/test-apiv2325
-rw-r--r--test/e2e/inspect_test.go13
-rw-r--r--vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go33
-rw-r--r--vendor/modules.txt4
63 files changed, 2037 insertions, 745 deletions
diff --git a/Makefile b/Makefile
index a7e779dd2..85f1036e0 100644
--- a/Makefile
+++ b/Makefile
@@ -263,7 +263,7 @@ localunit: test/goecho/goecho varlink_generate
ginkgo \
-r \
$(TESTFLAGS) \
- --skipPackage test/e2e,pkg/apparmor,test/endpoint \
+ --skipPackage test/e2e,pkg/apparmor,test/endpoint,pkg/bindings \
--cover \
--covermode atomic \
--tags "$(BUILDTAGS)" \
@@ -452,6 +452,8 @@ install.docker: docker-docs
install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(BINDIR) $(DESTDIR)$(MANDIR)/man1
install ${SELINUXOPT} -m 755 docker $(DESTDIR)$(BINDIR)/docker
install ${SELINUXOPT} -m 644 docs/build/man/docker*.1 -t $(DESTDIR)$(MANDIR)/man1
+ install ${SELINUXOPT} -m 755 -d ${DESTDIR}${SYSTEMDDIR} ${DESTDIR}${USERSYSTEMDDIR} ${DESTDIR}${TMPFILESDIR}
+ install ${SELINUXOPT} -m 644 contrib/systemd/system/podman-docker.conf -t ${DESTDIR}${TMPFILESDIR}
install.systemd:
install ${SELINUXOPT} -m 755 -d ${DESTDIR}${SYSTEMDDIR} ${DESTDIR}${USERSYSTEMDDIR} ${DESTDIR}${TMPFILESDIR}
diff --git a/cmd/podman/push.go b/cmd/podman/push.go
index 1be8dfe11..b078959ba 100644
--- a/cmd/podman/push.go
+++ b/cmd/podman/push.go
@@ -100,7 +100,8 @@ func pushCmd(c *cliconfig.PushValues) error {
// --compress and --format can only be used for the "dir" transport
splitArg := strings.SplitN(destName, ":", 2)
- if c.Flag("compress").Changed || c.Flag("format").Changed {
+
+ if c.IsSet("compress") || c.Flag("format").Changed {
if splitArg[0] != directory.Transport.Name() {
return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport")
}
@@ -141,7 +142,7 @@ func pushCmd(c *cliconfig.PushValues) error {
DockerRegistryCreds: registryCreds,
DockerCertPath: certPath,
}
- if c.Flag("tls-verify").Changed {
+ if c.IsSet("tls-verify") {
dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.TlsVerify)
}
diff --git a/contrib/systemd/system/podman-docker.conf b/contrib/systemd/system/podman-docker.conf
new file mode 100644
index 000000000..e12f19bce
--- /dev/null
+++ b/contrib/systemd/system/podman-docker.conf
@@ -0,0 +1 @@
+L+ /run/docker.sock - - - - /run/podman/podman.sock
diff --git a/docs/source/markdown/podman-build.1.md b/docs/source/markdown/podman-build.1.md
index fac8296ad..0f3bfa0d3 100644
--- a/docs/source/markdown/podman-build.1.md
+++ b/docs/source/markdown/podman-build.1.md
@@ -633,11 +633,11 @@ $ podman build .
$ podman build -f Containerfile.simple .
-$ cat ~/Dockerfile | podman build -f - .
+$ cat $HOME/Dockerfile | podman build -f - .
$ podman build -f Dockerfile.simple -f Containerfile.notsosimple .
-$ podman build -f Dockerfile.in ~
+$ podman build -f Dockerfile.in $HOME
$ podman build -t imageName .
@@ -649,7 +649,7 @@ $ podman build --runtime-flag log-format=json .
$ podman build --runtime-flag debug .
-$ podman build --authfile /tmp/auths/myauths.json --cert-dir ~/auth --tls-verify=true --creds=username:password -t imageName -f Dockerfile.simple .
+$ podman build --authfile /tmp/auths/myauths.json --cert-dir $HOME/auth --tls-verify=true --creds=username:password -t imageName -f Dockerfile.simple .
$ podman build --memory 40m --cpu-period 10000 --cpu-quota 50000 --ulimit nofile=1024:1028 -t imageName .
diff --git a/docs/source/markdown/podman-container-exists.1.md b/docs/source/markdown/podman-container-exists.1.md
index 4d988132b..3b4ca33e4 100644
--- a/docs/source/markdown/podman-container-exists.1.md
+++ b/docs/source/markdown/podman-container-exists.1.md
@@ -21,7 +21,7 @@ Print usage statement
Check if an container called `webclient` exists in local storage (the container does actually exist).
```
-$ sudo podman container exists webclient
+$ podman container exists webclient
$ echo $?
0
$
@@ -29,7 +29,7 @@ $
Check if an container called `webbackend` exists in local storage (the container does not actually exist).
```
-$ sudo podman container exists webbackend
+$ podman container exists webbackend
$ echo $?
1
$
diff --git a/docs/source/markdown/podman-exec.1.md b/docs/source/markdown/podman-exec.1.md
index 8c0106d70..1bd10f9ba 100644
--- a/docs/source/markdown/podman-exec.1.md
+++ b/docs/source/markdown/podman-exec.1.md
@@ -80,28 +80,28 @@ when creating the container.
The exit code from `podman exec` gives information about why the command within the container failed to run or why it exited. When `podman exec` exits with a
non-zero code, the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with Podman **_itself_**
+ **125** The error is with Podman itself
$ podman exec --foo ctrID /bin/sh; echo $?
Error: unknown flag: --foo
125
-**_126_** if the **_contained command_** cannot be invoked
+ **126** The _contained command_ cannot be invoked
$ podman exec ctrID /etc; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error
126
-**_127_** if the **_contained command_** cannot be found
+ **127** The _contained command_ cannot be found
$ podman exec ctrID foo; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error
127
-**_Exit code_** of **_contained command_** otherwise
+ **Exit code** The _contained command_ exit code
- $ podman exec ctrID /bin/sh -c 'exit 3'
- # 3
+ $ podman exec ctrID /bin/sh -c 'exit 3'; echo $?
+ 3
## EXAMPLES
diff --git a/docs/source/markdown/podman-history.1.md b/docs/source/markdown/podman-history.1.md
index 078864faa..1a8f8906c 100644
--- a/docs/source/markdown/podman-history.1.md
+++ b/docs/source/markdown/podman-history.1.md
@@ -29,17 +29,17 @@ Valid placeholders for the Go template are listed below:
## OPTIONS
-**--human**, **-H**
+**--human**, **-H**=*true|false*
-Display sizes and dates in human readable format
+Display sizes and dates in human readable format (default *true*).
-**--no-trunc**
+**--no-trunc**=*true|false*
-Do not truncate the output
+Do not truncate the output (default *false*).
-**--quiet**, **-q**
+**--quiet**, **-q**=*true|false*
-Print the numeric IDs only
+Print the numeric IDs only (default *false*).
**--format**=*format*
diff --git a/docs/source/markdown/podman-image-exists.1.md b/docs/source/markdown/podman-image-exists.1.md
index f6a89e2aa..3b7127b64 100644
--- a/docs/source/markdown/podman-image-exists.1.md
+++ b/docs/source/markdown/podman-image-exists.1.md
@@ -22,7 +22,7 @@ Print usage statement
Check if an image called `webclient` exists in local storage (the image does actually exist).
```
-$ sudo podman image exists webclient
+$ podman image exists webclient
$ echo $?
0
$
@@ -30,7 +30,7 @@ $
Check if an image called `webbackend` exists in local storage (the image does not actually exist).
```
-$ sudo podman image exists webbackend
+$ podman image exists webbackend
$ echo $?
1
$
diff --git a/docs/source/markdown/podman-remote.1.md b/docs/source/markdown/podman-remote.1.md
index bbc54a2a6..a7297f3f2 100644
--- a/docs/source/markdown/podman-remote.1.md
+++ b/docs/source/markdown/podman-remote.1.md
@@ -65,27 +65,27 @@ The exit code from `podman` gives information about why the container
failed to run or why it exited. When `podman` commands exit with a non-zero code,
the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with podman **_itself_**
+ **125** The error is with podman itself
$ podman run --foo busybox; echo $?
Error: unknown flag: --foo
- 125
+ 125
-**_126_** if executing a **_contained command_** and the **_command_** cannot be invoked
+ **126** Executing a _contained command_ and the _command_ cannot be invoked
$ podman run busybox /etc; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error
- 126
+ 126
-**_127_** if executing a **_contained command_** and the **_command_** cannot be found
+ **127** Executing a _contained command_ and the _command_ cannot be found
$ podman run busybox foo; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error
- 127
+ 127
-**_Exit code_** of **_contained command_** otherwise
+ **Exit code** _contained command_ exit code
- $ podman run busybox /bin/sh -c 'exit 3'
- # 3
+ $ podman run busybox /bin/sh -c 'exit 3'; echo $?
+ 3
## COMMANDS
@@ -135,7 +135,7 @@ the exit codes follow the `chroot` standard, see below:
## FILES
-**podman-remote.conf** (`~/.config/containers/podman-remote.conf`)
+**podman-remote.conf** (`$HOME/.config/containers/podman-remote.conf`)
The podman-remote.conf file is the default configuration file for the podman
remote client. It is in the TOML format. It is primarily used to keep track
diff --git a/docs/source/markdown/podman-rm.1.md b/docs/source/markdown/podman-rm.1.md
index 782feac6f..cddf06e3e 100644
--- a/docs/source/markdown/podman-rm.1.md
+++ b/docs/source/markdown/podman-rm.1.md
@@ -10,13 +10,13 @@ podman\-rm - Remove one or more containers
## DESCRIPTION
**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images.
-Running or unusable containers will not be removed without the `-f` option.
+Running or unusable containers will not be removed without the **-f** option.
## OPTIONS
**--all**, **-a**
-Remove all containers. Can be used in conjunction with -f as well.
+Remove all containers. Can be used in conjunction with **-f** as well.
**--cidfile**
@@ -46,51 +46,54 @@ The latest option is not supported on the remote client.
**--storage**
Remove the container from the storage library only.
-This is only possible with containers that are not present in libpod (cannot be seen by `podman ps`).
-It is used to remove containers from `podman build` and `buildah`, and orphan containers which were only partially removed by `podman rm`.
+This is only possible with containers that are not present in libpod (cannot be seen by **podman ps**).
+It is used to remove containers from **podman build** and **buildah**, and orphan containers which were only partially removed by **podman rm**.
The storage option conflicts with the **--all**, **--latest**, and **--volumes** options.
**--volumes**, **-v**
Remove anonymous volumes associated with the container. This does not include named volumes
-created with `podman volume create`, or the `--volume` option of `podman run` and `podman create`.
+created with **podman volume create**, or the **--volume** option of **podman run** and **podman create**.
## EXAMPLE
Remove a container by its name *mywebserver*
```
-podman rm mywebserver
+$ podman rm mywebserver
```
Remove several containers by name and container id.
```
-podman rm mywebserver myflaskserver 860a4b23
+$ podman rm mywebserver myflaskserver 860a4b23
```
Remove several containers reading their IDs from files.
```
-podman rm --cidfile ./cidfile-1 --cidfile /home/user/cidfile-2
+$ podman rm --cidfile ./cidfile-1 --cidfile /home/user/cidfile-2
```
Forcibly remove a container by container ID.
```
-podman rm -f 860a4b23
+$ podman rm -f 860a4b23
```
Remove all containers regardless of its run state.
```
-podman rm -f -a
+$ podman rm -f -a
```
Forcibly remove the latest container created.
```
-podman rm -f --latest
+$ podman rm -f --latest
```
## Exit Status
-**_0_** if all specified containers removed
-**_1_** if one of the specified containers did not exist, and no other failures
-**_2_** if one of the specified containers is paused or running
-**_125_** if the command fails for a reason other than container did not exist or is paused/running
+ **0** All specified containers removed
+
+ **1** One of the specified containers did not exist, and no other failures
+
+ **2** One of the specified containers is paused or running
+
+ **125** The command fails for a reason other than container did not exist or is paused/running
## SEE ALSO
podman(1), podman-image-rm(1)
diff --git a/docs/source/markdown/podman-rmi.1.md b/docs/source/markdown/podman-rmi.1.md
index 3c46bc32c..78ef2b157 100644
--- a/docs/source/markdown/podman-rmi.1.md
+++ b/docs/source/markdown/podman-rmi.1.md
@@ -24,27 +24,30 @@ This option will cause podman to remove all containers that are using the image
Remove an image by its short ID
```
-podman rmi c0ed59d05ff7
+$ podman rmi c0ed59d05ff7
```
Remove an image and its associated containers.
```
-podman rmi --force imageID
+$ podman rmi --force imageID
```
Remove multiple images by their shortened IDs.
```
-podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7
+$ podman rmi c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7
```
Remove all images and containers.
```
-podman rmi -a -f
+$ podman rmi -a -f
```
## Exit Status
-**_0_** if all specified images removed
-**_1_** if one of the specified images did not exist, and no other failures
-**_2_** if one of the specified images has child images or is being used by a container
-**_125_** if the command fails for a reason other than an image did not exist or is in use
+ **0** All specified images removed
+
+ **1** One of the specified images did not exist, and no other failures
+
+ **2** One of the specified images has child images or is being used by a container
+
+ **125** The command fails for a reason other than an image did not exist or is in use
## SEE ALSO
podman(1)
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 512a382a6..bf79ea031 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -1022,25 +1022,25 @@ The exit code from `podman run` gives information about why the container
failed to run or why it exited. When `podman run` exits with a non-zero code,
the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with Podman **_itself_**
+ **125** The error is with Podman itself
$ podman run --foo busybox; echo $?
Error: unknown flag: --foo
125
-**_126_** if the **_contained command_** cannot be invoked
+ **126** The _contained command_ cannot be invoked
$ podman run busybox /etc; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error
126
-**_127_** if the **_contained command_** cannot be found
+ **127** The _contained command_ cannot be found
$ podman run busybox foo; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error
127
-**_Exit code_** of **_contained command_** otherwise
+ **Exit code** _contained command_ exit code
$ podman run busybox /bin/sh -c 'exit 3'
3
diff --git a/docs/source/markdown/podman-stop.1.md b/docs/source/markdown/podman-stop.1.md
index 3b5f17057..7dbf18887 100644
--- a/docs/source/markdown/podman-stop.1.md
+++ b/docs/source/markdown/podman-stop.1.md
@@ -38,27 +38,27 @@ to run containers such as CRI-O, the last started container could be from either
The latest option is not supported on the remote client.
-**--timeout**, **--time**, **t**=*time*
+**--timeout**, **--time**, **-t**=*time*
Timeout to wait before forcibly stopping the container
-## EXAMPLE
+## EXAMPLES
-podman stop mywebserver
+$ podman stop mywebserver
-podman stop 860a4b235279
+$ podman stop 860a4b235279
-podman stop mywebserver 860a4b235279
+$ podman stop mywebserver 860a4b235279
-podman stop --cidfile /home/user/cidfile-1
+$ podman stop --cidfile /home/user/cidfile-1
-podman stop --cidfile /home/user/cidfile-1 --cidfile ./cidfile-2
+$ podman stop --cidfile /home/user/cidfile-1 --cidfile ./cidfile-2
-podman stop --timeout 2 860a4b235279
+$ podman stop --timeout 2 860a4b235279
-podman stop -a
+$ podman stop -a
-podman stop --latest
+$ podman stop --latest
## SEE ALSO
podman(1), podman-rm(1)
diff --git a/docs/source/markdown/podman-system-prune.1.md b/docs/source/markdown/podman-system-prune.1.md
index e6297dc0b..9c6ef5d8b 100644
--- a/docs/source/markdown/podman-system-prune.1.md
+++ b/docs/source/markdown/podman-system-prune.1.md
@@ -9,9 +9,9 @@ podman\-system\-prune - Remove all unused container, image and volume data
## DESCRIPTION
**podman system prune** removes all unused containers (both dangling and unreferenced), pods and optionally, volumes from local storage.
-With the `all` option, you can delete all unused images. Unused images are dangling images as well as any image that does not have any containers based on it.
+With the **--all** option, you can delete all unused images. Unused images are dangling images as well as any image that does not have any containers based on it.
-By default, volumes are not removed to prevent important data from being deleted if there is currently no container using the volume. Use the --volumes flag when running the command to prune volumes as well.
+By default, volumes are not removed to prevent important data from being deleted if there is currently no container using the volume. Use the **--volumes** flag when running the command to prune volumes as well.
## OPTIONS
**--all**, **-a**
@@ -28,7 +28,7 @@ Print usage statement
**--volumes**
-Prune volumes not used by at least one container
+Prune volumes currently unused by any container
## SEE ALSO
podman(1), podman-image-prune(1), podman-container-prune(1), podman-pod-prune(1), podman-volume-prune(1)
diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md
index 6e0eff045..fc069a7b0 100644
--- a/docs/source/markdown/podman.1.md
+++ b/docs/source/markdown/podman.1.md
@@ -108,7 +108,7 @@ Storage driver option, Default storage driver options are configured in /etc/con
Output logging information to syslog as well as the console.
-On remote clients, logging is directed to the file ~/.config/containers/podman.log
+On remote clients, logging is directed to the file $HOME/.config/containers/podman.log
**--tmpdir**
@@ -126,27 +126,27 @@ The exit code from `podman` gives information about why the container
failed to run or why it exited. When `podman` commands exit with a non-zero code,
the exit codes follow the `chroot` standard, see below:
-**_125_** if the error is with podman **_itself_**
+ **125** The error is with podman **_itself_**
$ podman run --foo busybox; echo $?
Error: unknown flag: --foo
- 125
+ 125
-**_126_** if executing a **_contained command_** and the **_command_** cannot be invoked
+ **126** Executing a _contained command_ and the _command_ cannot be invoked
$ podman run busybox /etc; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"/etc\": permission denied": OCI runtime error
- 126
+ 126
-**_127_** if executing a **_contained command_** and the **_command_** cannot be found
+ **127** Executing a _contained command_ and the _command_ cannot be found
$ podman run busybox foo; echo $?
Error: container_linux.go:346: starting container process caused "exec: \"foo\": executable file not found in $PATH": OCI runtime error
- 127
+ 127
-**_Exit code_** of **_contained command_** otherwise
+ **Exit code** _contained command_ exit code
- $ podman run busybox /bin/sh -c 'exit 3'
- # 3
+ $ podman run busybox /bin/sh -c 'exit 3'; echo $?
+ 3
## COMMANDS
@@ -266,9 +266,9 @@ Currently the slirp4netns package is required to be installed to create a networ
### **NOTE:** Unsupported file systems in rootless mode
-The Overlay file system (OverlayFS) is not supported in rootless mode. The fuse-overlayfs package is a tool that provides the functionality of OverlayFS in user namespace that allows mounting file systems in rootless environments. It is recommended to install the fuse-overlayfs package and to enable it by adding `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options]` in the `~/.config/containers/storage.conf` file.
+The Overlay file system (OverlayFS) is not supported in rootless mode. The fuse-overlayfs package is a tool that provides the functionality of OverlayFS in user namespace that allows mounting file systems in rootless environments. It is recommended to install the fuse-overlayfs package and to enable it by adding `mount_program = "/usr/bin/fuse-overlayfs"` under `[storage.options]` in the `$HOME/.config/containers/storage.conf` file.
-The Network File System (NFS) and other distributed file systems (for example: Lustre, Spectrum Scale, the General Parallel File System (GPFS)) are not supported when running in rootless mode as these file systems do not understand user namespace. However, rootless Podman can make use of an NFS Homedir by modifying the `~/.config/containers/storage.conf` to have the `graphroot` option point to a directory stored on local (Non NFS) storage.
+The Network File System (NFS) and other distributed file systems (for example: Lustre, Spectrum Scale, the General Parallel File System (GPFS)) are not supported when running in rootless mode as these file systems do not understand user namespace. However, rootless Podman can make use of an NFS Homedir by modifying the `$HOME/.config/containers/storage.conf` to have the `graphroot` option point to a directory stored on local (Non NFS) storage.
For more information, please refer to the [Podman Troubleshooting Page](https://github.com/containers/libpod/blob/master/troubleshooting.md).
diff --git a/docs/source/network.rst b/docs/source/network.rst
index d96e00a7d..e7848c90e 100644
--- a/docs/source/network.rst
+++ b/docs/source/network.rst
@@ -1,5 +1,5 @@
Network
-=====
+=======
:doc:`create <markdown/podman-network-create.1>` network create
diff --git a/go.mod b/go.mod
index c4e6d64bf..403f710fe 100644
--- a/go.mod
+++ b/go.mod
@@ -49,12 +49,12 @@ require (
github.com/opencontainers/runc v1.0.0-rc9
github.com/opencontainers/runtime-spec v0.1.2-0.20190618234442-a950415649c7
github.com/opencontainers/runtime-tools v0.9.0
- github.com/opencontainers/selinux v1.3.0
+ github.com/opencontainers/selinux v1.3.1
github.com/opentracing/opentracing-go v1.1.0
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0
- github.com/rootless-containers/rootlesskit v0.7.1
+ github.com/rootless-containers/rootlesskit v0.7.2
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
diff --git a/go.sum b/go.sum
index cf0cf98ac..8746215d5 100644
--- a/go.sum
+++ b/go.sum
@@ -405,6 +405,8 @@ github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4Ft
github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
github.com/opencontainers/selinux v1.3.0 h1:xsI95WzPZu5exzA6JzkLSfdr/DilzOhCJOqGe5TgR0g=
github.com/opencontainers/selinux v1.3.0/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
+github.com/opencontainers/selinux v1.3.1 h1:dn2Rc3wTEvTB6iVqoFrKKeMb0uZ38ZheeyMu2h5C1TI=
+github.com/opencontainers/selinux v1.3.1/go.mod h1:yTcKuYAh6R95iDpefGLQaPaRwJFwyzAJufJyiTt7s0g=
github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316 h1:enQG2QUGwug4fR1yM6hL0Fjzx6Km/exZY6RbSPwMu3o=
github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316/go.mod h1:dv+J0b/HWai0QnMVb37/H0v36klkLBi2TNpPeWDxX10=
github.com/openshift/api v3.9.1-0.20190810003144-27fb16909b15+incompatible h1:s55wx8JIG/CKnewev892HifTBrtKzMdvgB3rm4rxC2s=
@@ -456,6 +458,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/rootless-containers/rootlesskit v0.7.1 h1:enhwHIAXDjfpV83bL4xF60WZ+1ATjMB7spDDvpWAfPk=
github.com/rootless-containers/rootlesskit v0.7.1/go.mod h1:r9YL5mKRIdnwcYk4G8E5CSc9MDeFtgYmhfE4CSvDGYA=
+github.com/rootless-containers/rootlesskit v0.7.2 h1:gcWQ9/GN98ne1AqnoeOgQ8e6qpKd3BuB4ug+2h95Fr0=
+github.com/rootless-containers/rootlesskit v0.7.2/go.mod h1:r9YL5mKRIdnwcYk4G8E5CSc9MDeFtgYmhfE4CSvDGYA=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
@@ -629,6 +633,7 @@ golang.org/x/sys v0.0.0-20190902133755-9109b7679e13/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3 h1:7TYNF4UdlohbFwpNH04CoPMp1cHUZgO1Ebq5r2hIjfo=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2 h1:/J2nHFg1MTqaRLFO7M+J78ASNsJoz3r0cvHBPQ77fsE=
golang.org/x/sys v0.0.0-20191127021746-63cb32ae39b2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/install.md b/install.md
index 523f3b0f7..561c4afe9 100644
--- a/install.md
+++ b/install.md
@@ -35,8 +35,6 @@ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:st
sudo apt-key add - < Release.key
sudo apt-get update -qq
sudo apt-get -qq -y install podman
-sudo mkdir -p /etc/containers
-echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | sudo tee /etc/containers/registries.conf
```
There are many [packages](https://packages.debian.org/search?keywords=libpod&searchon=names&suite=stable&section=all)
@@ -100,8 +98,6 @@ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:st
sudo apt-key add - < Release.key
sudo apt-get update -qq
sudo apt-get -qq -y install podman
-sudo mkdir -p /etc/containers
-echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | sudo tee /etc/containers/registries.conf
```
@@ -169,8 +165,6 @@ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:te
sudo apt-key add - < Release.key
sudo apt-get update -qq
sudo apt-get -qq -y install podman
-sudo mkdir -p /etc/containers
-echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | sudo tee /etc/containers/registries.conf
```
@@ -187,6 +181,8 @@ If you use a newer Podman package from Fedora's `updates-testing`, we would
appreciate your `+1` feedback in [Bodhi, Fedora's update management
system](https://bodhi.fedoraproject.org/updates/?packages=podman).
+If you are running a non-rawhide Fedora distribution, you can also test the latest packages
+with our [COPR repository](https://copr.fedorainfracloud.org/coprs/baude/Upstream_CRIO_Family/).
#### [Raspbian](https://raspbian.org)
@@ -199,8 +195,6 @@ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:te
sudo apt-key add - < Release.key
sudo apt-get update -qq
sudo apt-get -qq -y install podman
-sudo mkdir -p /etc/containers
-echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | sudo tee /etc/containers/registries.conf
```
@@ -215,8 +209,6 @@ wget -nv https://download.opensuse.org/repositories/devel:kubic:libcontainers:te
sudo apt-key add - < Release.key
sudo apt-get update -qq
sudo apt-get -qq -y install podman
-sudo mkdir -p /etc/containers
-echo -e "[registries.search]\nregistries = ['docker.io', 'quay.io']" | sudo tee /etc/containers/registries.conf
```
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 6ea49e2a9..7ee8c45d7 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1012,6 +1012,15 @@ func (i *Image) Inspect(ctx context.Context) (*inspect.ImageData, error) {
History: ociv1Img.History,
NamesHistory: i.NamesHistory(),
}
+ if manifestType == manifest.DockerV2Schema2MediaType {
+ hc, err := i.GetHealthCheck(ctx)
+ if err != nil {
+ return nil, err
+ }
+ if hc != nil {
+ data.HealthCheck = hc
+ }
+ }
return data, nil
}
diff --git a/pkg/api/handlers/generic/ping.go b/pkg/api/handlers/generic/ping.go
index 44a67d53f..00afd86bc 100644
--- a/pkg/api/handlers/generic/ping.go
+++ b/pkg/api/handlers/generic/ping.go
@@ -3,6 +3,8 @@ package generic
import (
"fmt"
"net/http"
+
+ "github.com/containers/libpod/pkg/api/handlers"
)
func PingGET(w http.ResponseWriter, _ *http.Request) {
@@ -16,7 +18,7 @@ func PingHEAD(w http.ResponseWriter, _ *http.Request) {
}
func setHeaders(w http.ResponseWriter) {
- w.Header().Set("API-Version", DefaultApiVersion)
+ w.Header().Set("API-Version", handlers.DefaultApiVersion)
w.Header().Set("BuildKit-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go
index 9d2081cd8..8d499f40b 100644
--- a/pkg/api/handlers/utils/errors.go
+++ b/pkg/api/handlers/utils/errors.go
@@ -21,8 +21,9 @@ func Error(w http.ResponseWriter, apiMessage string, code int, err error) {
// Log detailed message of what happened to machine running podman service
log.Infof("Request Failed(%s): %s", http.StatusText(code), err.Error())
em := ErrorModel{
- Because: (errors.Cause(err)).Error(),
- Message: err.Error(),
+ Because: (errors.Cause(err)).Error(),
+ Message: err.Error(),
+ ResponseCode: code,
}
WriteJSON(w, code, em)
}
@@ -79,6 +80,8 @@ type ErrorModel struct {
// human error message, formatted for a human to read
// example: human error message
Message string `json:"message"`
+ // http response code
+ ResponseCode int `json:"response"`
}
func (e ErrorModel) Error() string {
@@ -89,6 +92,10 @@ func (e ErrorModel) Cause() error {
return errors.New(e.Because)
}
+func (e ErrorModel) Code() int {
+ return e.ResponseCode
+}
+
// UnsupportedParameter logs a given param by its string name as not supported.
func UnSupportedParameter(param string) {
log.Infof("API parameter %q: not supported", param)
diff --git a/pkg/api/handlers/generic/version.go b/pkg/api/handlers/version.go
index 39423914d..94166952c 100644
--- a/pkg/api/handlers/generic/version.go
+++ b/pkg/api/handlers/version.go
@@ -1,4 +1,4 @@
-package generic
+package handlers
import (
"fmt"
@@ -8,7 +8,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
docker "github.com/docker/docker/api/types"
"github.com/pkg/errors"
@@ -53,7 +52,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
},
}}
- utils.WriteResponse(w, http.StatusOK, handlers.Version{Version: docker.Version{
+ utils.WriteResponse(w, http.StatusOK, Version{Version: docker.Version{
Platform: struct {
Name string
}{
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index c59d3d379..f1cc0574c 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -195,7 +195,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: '#/responses/ConflictError'
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/images/name"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ r.Handle(VersionedPath("/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /images/{name}/get compat exportImage
// ---
// tags:
@@ -607,13 +607,13 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// summary: List Images
// description: Returns a list of images on the server
// parameters:
- // - name: "all"
- // in: "query"
- // description: "Show all images. Only images from a final layer (no children) are shown by default."
- // type: "boolean"
+ // - name: all
+ // in: query
+ // description: Show all images. Only images from a final layer (no children) are shown by default.
+ // type: boolean
// default: false
- // - name: "filters"
- // in: "query"
+ // - name: filters
+ // in: query
// description: |
// A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters:
// - `before`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
@@ -621,12 +621,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - `label=key` or `label="key=value"` of an image label
// - `reference`=(`<image-name>[:<tag>]`)
// - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
- // type: "string"
- // - name: "digests"
- // in: "query"
- // description: Not supported
- // type: "boolean"
- // default: false
+ // type: string
// produces:
// - application/json
// responses:
@@ -753,7 +748,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// $ref: '#/responses/ConflictError'
// 500:
// $ref: '#/responses/InternalError'
- r.Handle(VersionedPath("/libpod/images/name"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
+ r.Handle(VersionedPath("/libpod/images/{name}"), APIHandler(s.Context, handlers.RemoveImage)).Methods(http.MethodDelete)
// swagger:operation GET /libpod/images/{name}/get libpod libpoodExportImage
// ---
// tags:
diff --git a/pkg/api/server/register_version.go b/pkg/api/server/register_version.go
index 94216b1b6..d3b47c2a9 100644
--- a/pkg/api/server/register_version.go
+++ b/pkg/api/server/register_version.go
@@ -1,12 +1,12 @@
package server
import (
- "github.com/containers/libpod/pkg/api/handlers/generic"
+ "github.com/containers/libpod/pkg/api/handlers"
"github.com/gorilla/mux"
)
func (s *APIServer) registerVersionHandlers(r *mux.Router) error {
- r.Handle("/version", APIHandler(s.Context, generic.VersionHandler))
- r.Handle(VersionedPath("/version"), APIHandler(s.Context, generic.VersionHandler))
+ r.Handle("/version", APIHandler(s.Context, handlers.VersionHandler))
+ r.Handle(VersionedPath("/version"), APIHandler(s.Context, handlers.VersionHandler))
return nil
}
diff --git a/pkg/apparmor/apparmor_linux_template.go b/pkg/apparmor/apparmor_linux_template.go
index 163ba3792..8d9a92ef7 100644
--- a/pkg/apparmor/apparmor_linux_template.go
+++ b/pkg/apparmor/apparmor_linux_template.go
@@ -17,6 +17,12 @@ profile {{.Name}} flags=(attach_disconnected,mediate_deleted) {
file,
umount,
+{{if ge .Version 208096}}
+ # Allow signals from privileged profiles and from within the same profile
+ signal (receive) peer=unconfined,
+ signal (send,receive) peer={{.Name}},
+{{end}}
+
deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index 551a63c62..3dec6ca20 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,14 +1,22 @@
package bindings
import (
+ "context"
"fmt"
"io"
+ "net"
"net/http"
+ "net/url"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ jsoniter "github.com/json-iterator/go"
+ "github.com/pkg/errors"
)
-const (
- defaultConnection string = "http://localhost:8080/v1.24/libpod"
- pingConnection string = "http://localhost:8080/_ping"
+var (
+ defaultConnectionPath string = filepath.Join(fmt.Sprintf("v%s", handlers.MinimalApiVersion), "libpod")
)
type APIResponse struct {
@@ -17,46 +25,170 @@ type APIResponse struct {
}
type Connection struct {
- url string
- client *http.Client
+ scheme string
+ address string
+ client *http.Client
}
-func NewConnection(url string) (Connection, error) {
- if len(url) < 1 {
- url = defaultConnection
+// NewConnection takes a URI as a string and returns a context with the
+// Connection embedded as a value. This context needs to be passed to each
+// endpoint to work correctly.
+//
+// A valid URI connection should be scheme://
+// For example tcp://localhost:<port>
+// or unix://run/podman/podman.sock
+func NewConnection(uri string) (context.Context, error) {
+ u, err := url.Parse(uri)
+ if err != nil {
+ return nil, err
}
- newConn := Connection{
- url: url,
- client: &http.Client{},
+ // TODO once ssh is implemented, remove this block and
+ // add it to the conditional beneath it
+ if u.Scheme == "ssh" {
+ return nil, ErrNotImplemented
+ }
+ if u.Scheme != "tcp" && u.Scheme != "unix" {
+ return nil, errors.Errorf("%s is not a support schema", u.Scheme)
+ }
+
+ if u.Scheme == "tcp" && !strings.HasPrefix(uri, "tcp://") {
+ return nil, errors.New("tcp URIs should begin with tcp://")
+ }
+
+ address := u.Path
+ if u.Scheme == "tcp" {
+ address = u.Host
+ }
+ newConn := newConnection(u.Scheme, address)
+ ctx := context.WithValue(context.Background(), "conn", &newConn)
+ if err := pingNewConnection(ctx); err != nil {
+ return nil, err
}
- response, err := http.Get(pingConnection)
+ return ctx, nil
+}
+
+// pingNewConnection pings to make sure the RESTFUL service is up
+// and running. it should only be used where initializing a connection
+func pingNewConnection(ctx context.Context) error {
+ conn, err := GetConnectionFromContext(ctx)
if err != nil {
- return newConn, err
+ return err
+ }
+ // the ping endpoint sits at / in this case
+ response, err := conn.DoRequest(nil, http.MethodGet, "../../../_ping", nil)
+ if err != nil {
+ return err
+ }
+ if response.StatusCode == http.StatusOK {
+ return nil
}
- if err := response.Body.Close(); err != nil {
- return newConn, err
+ return errors.Errorf("ping response was %q", response.StatusCode)
+}
+
+// newConnection takes a scheme and address and creates a connection from it
+func newConnection(scheme, address string) Connection {
+ client := http.Client{
+ Transport: &http.Transport{
+ DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
+ return net.Dial(scheme, address)
+ },
+ },
}
- return newConn, err
+ newConn := Connection{
+ client: &client,
+ address: address,
+ scheme: scheme,
+ }
+ return newConn
}
-func (c Connection) makeEndpoint(u string) string {
- return fmt.Sprintf("%s%s", defaultConnection, u)
+func (c *Connection) makeEndpoint(u string) string {
+ // The d character in the url is discarded and is meaningless
+ return fmt.Sprintf("http://d/%s%s", defaultConnectionPath, u)
}
-func (c Connection) newRequest(httpMethod, endpoint string, httpBody io.Reader, params map[string]string) (*APIResponse, error) {
- e := c.makeEndpoint(endpoint)
+// DoRequest assembles the http request and returns the response
+func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, queryParams map[string]string, pathValues ...string) (*APIResponse, error) {
+ var (
+ err error
+ response *http.Response
+ )
+ safePathValues := make([]interface{}, len(pathValues))
+ // Make sure path values are http url safe
+ for _, pv := range pathValues {
+ safePathValues = append(safePathValues, url.QueryEscape(pv))
+ }
+ safeEndpoint := fmt.Sprintf(endpoint, safePathValues...)
+
+ e := c.makeEndpoint(safeEndpoint)
req, err := http.NewRequest(httpMethod, e, httpBody)
if err != nil {
return nil, err
}
- if len(params) > 0 {
+ if len(queryParams) > 0 {
// if more desirable we could use url to form the encoded endpoint with params
r := req.URL.Query()
- for k, v := range params {
- r.Add(k, v)
+ for k, v := range queryParams {
+ r.Add(k, url.QueryEscape(v))
}
req.URL.RawQuery = r.Encode()
}
- response, err := c.client.Do(req) // nolint
+ // Give the Do three chances in the case of a comm/service hiccup
+ for i := 0; i < 3; i++ {
+ response, err = c.client.Do(req) // nolint
+ if err == nil {
+ break
+ }
+ }
return &APIResponse{response, req}, err
}
+
+// GetConnectionFromContext returns a bindings connection from the context
+// being passed into each method.
+func GetConnectionFromContext(ctx context.Context) (*Connection, error) {
+ c := ctx.Value("conn")
+ if c == nil {
+ return nil, errors.New("unable to get connection from context")
+ }
+ conn := c.(Connection)
+ return &conn, nil
+}
+
+// FiltersToHTML converts our typical filter format of a
+// map[string][]string to a query/html safe string.
+func FiltersToHTML(filters map[string][]string) (string, error) {
+ lowerCaseKeys := make(map[string][]string)
+ for k, v := range filters {
+ lowerCaseKeys[strings.ToLower(k)] = v
+ }
+ unsafeString, err := jsoniter.MarshalToString(lowerCaseKeys)
+ if err != nil {
+ return "", err
+ }
+ return url.QueryEscape(unsafeString), nil
+}
+
+// IsInformation returns true if the response code is 1xx
+func (h *APIResponse) IsInformational() bool {
+ return h.Response.StatusCode/100 == 1
+}
+
+// IsSuccess returns true if the response code is 2xx
+func (h *APIResponse) IsSuccess() bool {
+ return h.Response.StatusCode/100 == 2
+}
+
+// IsRedirection returns true if the response code is 3xx
+func (h *APIResponse) IsRedirection() bool {
+ return h.Response.StatusCode/100 == 3
+}
+
+// IsClientError returns true if the response code is 4xx
+func (h *APIResponse) IsClientError() bool {
+ return h.Response.StatusCode/100 == 4
+}
+
+// IsServerError returns true if the response code is 5xx
+func (h *APIResponse) IsServerError() bool {
+ return h.Response.StatusCode/100 == 5
+}
diff --git a/pkg/bindings/containers.go b/pkg/bindings/containers.go
deleted file mode 100644
index 057580088..000000000
--- a/pkg/bindings/containers.go
+++ /dev/null
@@ -1,139 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/cmd/podman/shared"
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) ListContainers(filter []string, last int, size, sync bool) ([]shared.PsContainerOutput, error) { // nolint:typecheck
- images := []shared.PsContainerOutput{}
- params := make(map[string]string)
- params["last"] = strconv.Itoa(last)
- params["size"] = strconv.FormatBool(size)
- params["sync"] = strconv.FormatBool(sync)
- response, err := c.newRequest(http.MethodGet, "/containers/json", nil, params)
- if err != nil {
- return images, err
- }
- return images, response.Process(nil)
-}
-
-func (c Connection) PruneContainers() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/containers/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(nil)
-}
-
-func (c Connection) RemoveContainer(nameOrID string, force, volumes bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- params["vols"] = strconv.FormatBool(volumes)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/containers/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) InspectContainer(nameOrID string, size bool) (*libpod.InspectContainerData, error) {
- params := make(map[string]string)
- params["size"] = strconv.FormatBool(size)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/json", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- inspect := libpod.InspectContainerData{}
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillContainer(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-
-}
-func (c Connection) ContainerLogs() {}
-func (c Connection) PauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RestartContainer(nameOrID string, timeout int) error {
- // TODO how do we distinguish between an actual zero value and not wanting to change the timeout value
- params := make(map[string]string)
- params["timeout"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/restart", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartContainer(nameOrID, detachKeys string) error {
- params := make(map[string]string)
- if len(detachKeys) > 0 {
- params["detachKeys"] = detachKeys
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/start", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ContainerStats() {}
-func (c Connection) ContainerTop() {}
-
-func (c Connection) UnpauseContainer(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) WaitContainer(nameOrID string) error {
- // TODO when returns are ironed out, we can should use the newRequest approach
- _, err := http.Post(c.makeEndpoint(fmt.Sprintf("containers/%s/wait", nameOrID)), "application/json", nil) // nolint
- return err
-}
-
-func (c Connection) ContainerExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/containers/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) StopContainer(nameOrID string, timeout *int) error {
- params := make(map[string]string)
- if timeout != nil {
- params["t"] = strconv.Itoa(*timeout)
- }
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
new file mode 100644
index 000000000..334a656d4
--- /dev/null
+++ b/pkg/bindings/containers/containers.go
@@ -0,0 +1,255 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/cmd/podman/shared"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// List obtains a list of containers in local storage. All parameters to this method are optional.
+// The filters are used to determine which containers are listed. The last parameter indicates to only return
+// the most recent number of containers. The pod and size booleans indicate that pod information and rootfs
+// size information should also be included. Finally, the sync bool synchronizes the OCI runtime and
+// container state.
+func List(ctx context.Context, filters map[string][]string, last *int, pod, size, sync *bool) ([]*shared.PsContainerOutput, error) { // nolint:typecheck
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var images []*shared.PsContainerOutput
+ params := make(map[string]string)
+ if last != nil {
+ params["last"] = strconv.Itoa(*last)
+ }
+ if pod != nil {
+ params["pod"] = strconv.FormatBool(*pod)
+ }
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ if sync != nil {
+ params["sync"] = strconv.FormatBool(*sync)
+ }
+ if filters != nil {
+ filterString, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = filterString
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/json", params)
+ if err != nil {
+ return images, err
+ }
+ return images, response.Process(nil)
+}
+
+// Prune removes stopped and exited containers from local storage. The optional filters can be
+// used for more granular selection of containers. The main error returned indicates if there were runtime
+// errors like finding containers. Errors specific to the removal of a container are in the PruneContainerResponse
+// structure.
+func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
+ var (
+ pruneResponse []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ filterString, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = filterString
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/prune", params)
+ if err != nil {
+ return pruneResponse, err
+ }
+ return pruneResponse, response.Process(pruneResponse)
+}
+
+// Remove removes a container from local storage. The force bool designates
+// that the container should be removed forcibly (example, even it is running). The volumes
+// bool dictates that a container's volumes should also be removed.
+func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ if volumes != nil {
+ params["vols"] = strconv.FormatBool(*volumes)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/containers/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Inspect returns low level information about a Container. The nameOrID can be a container name
+// or a partial/full ID. The size bool determines whether the size of the container's root filesystem
+// should be calculated. Calculating the size of a container requires extra work from the filesystem and
+// is therefore slower.
+func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectContainerData, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/json", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.InspectContainerData{}
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a given signal to a given container. The signal should be the string
+// representation of a signal like 'SIGKILL'. The nameOrID can be a container name
+// or a partial/full ID
+func Kill(ctx context.Context, nameOrID string, signal string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ params["signal"] = signal
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+
+}
+func Logs() {}
+
+// Pause pauses a given container. The nameOrID can be a container name
+// or a partial/full ID.
+func Pause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/pause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Restart restarts a running container. The nameOrID can be a container name
+// or a partial/full ID. The optional timeout specifies the number of seconds to wait
+// for the running container to stop before killing it.
+func Restart(ctx context.Context, nameOrID string, timeout *int) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restart", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Start starts a non-running container.The nameOrID can be a container name
+// or a partial/full ID. The optional parameter for detach keys are to override the default
+// detach key sequence.
+func Start(ctx context.Context, nameOrID string, detachKeys *string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if detachKeys != nil {
+ params["detachKeys"] = *detachKeys
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/start", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Stats() {}
+func Top() {}
+
+// Unpause resumes the given paused container. The nameOrID can be a container name
+// or a partial/full ID.
+func Unpause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Wait blocks until the given container exits and returns its exit code. The nameOrID can be a container name
+// or a partial/full ID.
+func Wait(ctx context.Context, nameOrID string) (int32, error) {
+ var exitCode int32
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return exitCode, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "containers/%s/wait", nil, nameOrID)
+ if err != nil {
+ return exitCode, err
+ }
+ return exitCode, response.Process(&exitCode)
+}
+
+// Exists is a quick, light-weight way to determine if a given container
+// exists in local storage. The nameOrID can be a container name
+// or a partial/full ID.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "containers/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Stop stops a running container. The timeout is optional. The nameOrID can be a container name
+// or a partial/full ID
+func Stop(ctx context.Context, nameOrID string, timeout *int) error {
+ params := make(map[string]string)
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go
new file mode 100644
index 000000000..9ed7f858d
--- /dev/null
+++ b/pkg/bindings/containers/healthcheck.go
@@ -0,0 +1,26 @@
+package containers
+
+import (
+ "context"
+ "github.com/containers/libpod/pkg/bindings"
+ "net/http"
+
+ "github.com/containers/libpod/libpod"
+)
+
+// RunHealthCheck executes the container's healthcheck and returns the health status of the
+// container.
+func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckStatus, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ var (
+ status libpod.HealthCheckStatus
+ )
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/runhealthcheck", nil, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return &status, response.Process(&status)
+}
diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go
new file mode 100644
index 000000000..d68dee981
--- /dev/null
+++ b/pkg/bindings/containers/mount.go
@@ -0,0 +1,53 @@
+package containers
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Mount mounts an existing container to the filesystem. It returns the path
+// of the mounted container in string format.
+func Mount(ctx context.Context, nameOrID string) (string, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return "", err
+ }
+ var (
+ path string
+ )
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/mount", nil, nameOrID)
+ if err != nil {
+ return path, err
+ }
+ return path, response.Process(&path)
+}
+
+// Unmount unmounts a container from the filesystem. The container must not be running
+// or the unmount will fail.
+func Unmount(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/unmount", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// GetMountedContainerPaths returns a map of mounted containers and their mount locations.
+func GetMountedContainerPaths(ctx context.Context) (map[string]string, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ mounts := make(map[string]string)
+ response, err := conn.DoRequest(nil, http.MethodGet, "/containers/showmounted", nil)
+ if err != nil {
+ return mounts, err
+ }
+ return mounts, response.Process(&mounts)
+}
diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go
index 9a02925a3..8bd40f804 100644
--- a/pkg/bindings/errors.go
+++ b/pkg/bindings/errors.go
@@ -7,7 +7,6 @@ import (
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/pkg/errors"
- "github.com/sirupsen/logrus"
)
var (
@@ -37,10 +36,10 @@ func (a APIResponse) Process(unmarshalInto interface{}) error {
return handleError(data)
}
-func closeResponseBody(r *http.Response) {
- if r != nil {
- if err := r.Body.Close(); err != nil {
- logrus.Error(errors.Wrap(err, "unable to close response body"))
- }
+func CheckResponseCode(inError error) (int, error) {
+ e, ok := inError.(utils.ErrorModel)
+ if !ok {
+ return -1, errors.New("error is not type ErrorModel")
}
+ return e.Code(), nil
}
diff --git a/pkg/bindings/generate.go b/pkg/bindings/generate.go
deleted file mode 100644
index 534909062..000000000
--- a/pkg/bindings/generate.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package bindings
-
-func (c Connection) GenerateKube() {}
-func (c Connection) GenerateSystemd() {}
diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go
new file mode 100644
index 000000000..2916754b8
--- /dev/null
+++ b/pkg/bindings/generate/generate.go
@@ -0,0 +1,4 @@
+package generate
+
+func GenerateKube() {}
+func GenerateSystemd() {}
diff --git a/pkg/bindings/healthcheck.go b/pkg/bindings/healthcheck.go
deleted file mode 100644
index 32515e332..000000000
--- a/pkg/bindings/healthcheck.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) RunHealthCheck(nameOrID string) (*libpod.HealthCheckStatus, error) {
- var (
- status libpod.HealthCheckStatus
- )
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/containers/%s/runhealthcheck", nameOrID), nil, nil)
- if err != nil {
- return nil, err
- }
- return &status, response.Process(&status)
-}
diff --git a/pkg/bindings/images.go b/pkg/bindings/images.go
deleted file mode 100644
index 3abc8c372..000000000
--- a/pkg/bindings/images.go
+++ /dev/null
@@ -1,111 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "io"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/pkg/api/handlers"
- "github.com/containers/libpod/pkg/inspect"
-)
-
-func (c Connection) ImageExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/images/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- if response.StatusCode == http.StatusOK {
- return true, nil
- }
- return false, nil
-}
-
-func (c Connection) ListImages() ([]handlers.ImageSummary, error) {
- imageSummary := []handlers.ImageSummary{}
- response, err := c.newRequest(http.MethodGet, "/images/json", nil, nil)
- if err != nil {
- return imageSummary, err
- }
- return imageSummary, response.Process(&imageSummary)
-}
-
-func (c Connection) GetImage(nameOrID string) (*inspect.ImageData, error) {
- inspectedData := inspect.ImageData{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspectedData, err
- }
- return &inspectedData, response.Process(&inspectedData)
-}
-
-func (c Connection) ImageTree(nameOrId string) error {
- return ErrNotImplemented
-}
-
-func (c Connection) ImageHistory(nameOrID string) ([]handlers.HistoryResponse, error) {
- history := []handlers.HistoryResponse{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/history", nameOrID), nil, nil)
- if err != nil {
- return history, err
- }
- return history, response.Process(&history)
-}
-
-func (c Connection) LoadImage(r io.Reader) error {
- // TODO this still needs error handling added
- _, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint
- return err
-}
-
-func (c Connection) RemoveImage(nameOrID string, force bool) ([]map[string]string, error) {
- deletes := []map[string]string{}
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/images/%s", nameOrID), nil, params)
- if err != nil {
- return nil, err
- }
- return deletes, response.Process(&deletes)
-}
-
-func (c Connection) ExportImage(nameOrID string, w io.Writer, format string, compress bool) error {
- params := make(map[string]string)
- params["format"] = format
- params["compress"] = strconv.FormatBool(compress)
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/images/%s/get", nameOrID), nil, params)
- if err != nil {
- return err
- }
- if err := response.Process(nil); err != nil {
- return err
- }
- _, err = io.Copy(w, response.Body)
- return err
-}
-
-func (c Connection) PruneImages(all bool, filters []string) ([]string, error) {
- var (
- deleted []string
- )
- params := make(map[string]string)
- // FIXME How do we do []strings?
- //params["filters"] = format
- response, err := c.newRequest(http.MethodPost, "/images/prune", nil, params)
- if err != nil {
- return deleted, err
- }
- return deleted, response.Process(nil)
-}
-
-func (c Connection) TagImage(nameOrID string) error {
- var ()
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/images/%s/tag", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) BuildImage(nameOrId string) {}
diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go
new file mode 100644
index 000000000..deaf93f0e
--- /dev/null
+++ b/pkg/bindings/images/images.go
@@ -0,0 +1,187 @@
+package images
+
+import (
+ "context"
+ "io"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/inspect"
+)
+
+// Exists a lightweight way to determine if an image exists in local storage. It returns a
+// boolean response.
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// List returns a list of images in local storage. The all boolean and filters parameters are optional
+// ways to alter the image query.
+func List(ctx context.Context, all *bool, filters map[string][]string) ([]*handlers.ImageSummary, error) {
+ var imageSummary []*handlers.ImageSummary
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if all != nil {
+ params["all"] = strconv.FormatBool(*all)
+ }
+ if filters != nil {
+ strFilters, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = strFilters
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/json", params)
+ if err != nil {
+ return imageSummary, err
+ }
+ return imageSummary, response.Process(&imageSummary)
+}
+
+// Get performs an image inspect. To have the on-disk size of the image calculated, you can
+// use the optional size parameter.
+func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageData, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if size != nil {
+ params["size"] = strconv.FormatBool(*size)
+ }
+ inspectedData := inspect.ImageData{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID)
+ if err != nil {
+ return &inspectedData, err
+ }
+ return &inspectedData, response.Process(&inspectedData)
+}
+
+func ImageTree(ctx context.Context, nameOrId string) error {
+ return bindings.ErrNotImplemented
+}
+
+// History returns the parent layers of an image.
+func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, error) {
+ var history []*handlers.HistoryResponse
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/history", nil, nameOrID)
+ if err != nil {
+ return history, err
+ }
+ return history, response.Process(&history)
+}
+
+func Load(ctx context.Context, r io.Reader) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ // TODO this still needs error handling added
+ //_, err := http.Post(c.makeEndpoint("/images/loads"), "application/json", r) //nolint
+ _ = conn
+ return bindings.ErrNotImplemented
+}
+
+// Remove deletes an image from local storage. The optional force parameter will forcibly remove
+// the image by removing all all containers, including those that are Running, first.
+func Remove(ctx context.Context, nameOrID string, force *bool) ([]map[string]string, error) {
+ var deletes []map[string]string
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/images/%s", params, nameOrID)
+ if err != nil {
+ return nil, err
+ }
+ return deletes, response.Process(&deletes)
+}
+
+// Export saves an image from local storage as a tarball or image archive. The optional format
+// parameter is used to change the format of the output.
+func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if format != nil {
+ params["format"] = *format
+ }
+ if compress != nil {
+ params["compress"] = strconv.FormatBool(*compress)
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/get", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ if err := response.Process(nil); err != nil {
+ return err
+ }
+ _, err = io.Copy(w, response.Body)
+ return err
+}
+
+// Prune removes unused images from local storage. The optional filters can be used to further
+// define which images should be pruned.
+func Prune(ctx context.Context, filters map[string][]string) ([]string, error) {
+ var (
+ deleted []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/prune", params)
+ if err != nil {
+ return deleted, err
+ }
+ return deleted, response.Process(nil)
+}
+
+// Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required.
+func Tag(ctx context.Context, nameOrID, tag, repo string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ params["tag"] = tag
+ params["repo"] = repo
+ response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/tag", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Build(nameOrId string) {}
diff --git a/pkg/bindings/images/search.go b/pkg/bindings/images/search.go
new file mode 100644
index 000000000..d98ddf18d
--- /dev/null
+++ b/pkg/bindings/images/search.go
@@ -0,0 +1,40 @@
+package images
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Search looks for the given image (term) in container image registries. The optional limit parameter sets
+// a maximum number of results returned. The optional filters parameter allow for more specific image
+// searches.
+func Search(ctx context.Context, term string, limit *int, filters map[string][]string) ([]image.SearchResult, error) {
+ var (
+ searchResults []image.SearchResult
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ params["term"] = term
+ if limit != nil {
+ params["limit"] = strconv.Itoa(*limit)
+ }
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/images/search", params)
+ if err != nil {
+ return searchResults, nil
+ }
+ return searchResults, response.Process(&searchResults)
+}
diff --git a/pkg/bindings/mount.go b/pkg/bindings/mount.go
deleted file mode 100644
index 2e3d6d7f6..000000000
--- a/pkg/bindings/mount.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-)
-
-func (c Connection) MountContainer(nameOrID string) (string, error) {
- var (
- path string
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/containers/%s/mount", nameOrID), nil, nil)
- if err != nil {
- return path, err
- }
- return path, response.Process(&path)
-}
-
-func (c Connection) GetMountedContainerPaths() (map[string]string, error) {
- mounts := make(map[string]string)
- response, err := c.newRequest(http.MethodGet, "/containers/showmounted", nil, nil)
- if err != nil {
- return mounts, err
- }
- return mounts, response.Process(&mounts)
-}
diff --git a/pkg/bindings/network.go b/pkg/bindings/network.go
deleted file mode 100644
index 383615e5d..000000000
--- a/pkg/bindings/network.go
+++ /dev/null
@@ -1,37 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
-
- "github.com/containernetworking/cni/libcni"
-)
-
-func (c Connection) CreateNetwork() {}
-func (c Connection) InspectNetwork(nameOrID string) (map[string]interface{}, error) {
- n := make(map[string]interface{})
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/networks/%s/json", nameOrID), nil, nil)
- if err != nil {
- return n, err
- }
- return n, response.Process(&n)
-}
-
-func (c Connection) RemoveNetwork(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/networks/%s", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListNetworks() ([]*libcni.NetworkConfigList, error) {
- var (
- netList []*libcni.NetworkConfigList
- )
- response, err := c.newRequest(http.MethodGet, "/networks/json", nil, nil)
- if err != nil {
- return netList, err
- }
- return netList, response.Process(&netList)
-}
diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go
new file mode 100644
index 000000000..97bbb8c42
--- /dev/null
+++ b/pkg/bindings/network/network.go
@@ -0,0 +1,50 @@
+package network
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/containernetworking/cni/libcni"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+func Create() {}
+func Inspect(ctx context.Context, nameOrID string) (map[string]interface{}, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ n := make(map[string]interface{})
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/%s/json", nil, nameOrID)
+ if err != nil {
+ return n, err
+ }
+ return n, response.Process(&n)
+}
+
+func Remove(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/networks/%s", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func List(ctx context.Context) ([]*libcni.NetworkConfigList, error) {
+ var (
+ netList []*libcni.NetworkConfigList
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil)
+ if err != nil {
+ return netList, err
+ }
+ return netList, response.Process(&netList)
+}
diff --git a/pkg/bindings/play.go b/pkg/bindings/play.go
deleted file mode 100644
index a9dee82b1..000000000
--- a/pkg/bindings/play.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package bindings
-
-func (c Connection) PlayKube() {}
diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go
new file mode 100644
index 000000000..a6f03cad2
--- /dev/null
+++ b/pkg/bindings/play/play.go
@@ -0,0 +1,7 @@
+package play
+
+import "github.com/containers/libpod/pkg/bindings"
+
+func PlayKube() error {
+ return bindings.ErrNotImplemented
+}
diff --git a/pkg/bindings/pods.go b/pkg/bindings/pods.go
deleted file mode 100644
index 704d71477..000000000
--- a/pkg/bindings/pods.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
-)
-
-func (c Connection) CreatePod() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) PodExists(nameOrID string) (bool, error) {
- response, err := http.Get(c.makeEndpoint(fmt.Sprintf("/pods/%s/exists", nameOrID))) // nolint
- defer closeResponseBody(response)
- if err != nil {
- return false, err
- }
- return response.StatusCode == http.StatusOK, err
-}
-
-func (c Connection) InspectPod(nameOrID string) (*libpod.PodInspect, error) {
- inspect := libpod.PodInspect{}
- response, err := c.newRequest(http.MethodGet, fmt.Sprintf("/pods/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) KillPod(nameOrID string, signal int) error {
- params := make(map[string]string)
- params["signal"] = strconv.Itoa(signal)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/kill", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/pause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PrunePods(force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) ListPods(filters []string) (*[]libpod.PodInspect, error) {
- var (
- inspect []libpod.PodInspect
- )
- params := make(map[string]string)
- // TODO I dont remember how to do this for []string{}
- // FIXME
- //params["filters"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/pods/json", nil, params)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) RestartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/restart", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) RemovePod(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) StartPod(nameOrID string) error {
- response, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/pods/%s/start", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodStats() error {
- // TODO
- return ErrNotImplemented
-}
-
-func (c Connection) StopPod(nameOrID string, timeout int) error {
- params := make(map[string]string)
- params["t"] = strconv.Itoa(timeout)
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/stop", nameOrID), nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
-
-func (c Connection) PodTop() error {
- // TODO
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) UnpausePod(nameOrID string) error {
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/pods/%s/unpause", nameOrID), nil, nil)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go
new file mode 100644
index 000000000..a6b74c21d
--- /dev/null
+++ b/pkg/bindings/pods/pods.go
@@ -0,0 +1,196 @@
+package pods
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+func CreatePod() error {
+ // TODO
+ return bindings.ErrNotImplemented
+}
+
+// Exists is a lightweight method to determine if a pod exists in local storage
+func Exists(ctx context.Context, nameOrID string) (bool, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return false, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/exists", nil, nameOrID)
+ if err != nil {
+ return false, err
+ }
+ return response.IsSuccess(), nil
+}
+
+// Inspect returns low-level information about the given pod.
+func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ inspect := libpod.PodInspect{}
+ response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter
+// can be used to override SIGTERM.
+func Kill(ctx context.Context, nameOrID string, signal *string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if signal != nil {
+ params["signal"] = *signal
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Pause pauses all running containers in a given pod.
+func Pause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Prune removes all non-running pods in local storage.
+func Prune(ctx context.Context) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/prune", nil)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// List returns all pods in local storage. The optional filters parameter can
+// be used to refine which pods should be listed.
+func List(ctx context.Context, filters map[string][]string) (*[]libpod.PodInspect, error) {
+ var (
+ inspect []libpod.PodInspect
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ params := make(map[string]string)
+ if filters != nil {
+ stringFilter, err := bindings.FiltersToHTML(filters)
+ if err != nil {
+ return nil, err
+ }
+ params["filters"] = stringFilter
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/json", params)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+// Restart restarts all containers in a pod.
+func Restart(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Remove deletes a Pod from from local storage. The optional force parameter denotes
+// that the Pod can be removed even if in a running state.
+func Remove(ctx context.Context, nameOrID string, force *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+// Start starts all containers in a pod.
+func Start(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s/start", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Stats() error {
+ // TODO
+ return bindings.ErrNotImplemented
+}
+
+// Stop stops all containers in a Pod. The optional timeout parameter can be
+// used to override the timeout before the container is killed.
+func Stop(ctx context.Context, nameOrID string, timeout *int) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if timeout != nil {
+ params["t"] = strconv.Itoa(*timeout)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
+
+func Top() error {
+ // TODO
+ return bindings.ErrNotImplemented // nolint:typecheck
+}
+
+// Unpause unpauses all paused containers in a Pod.
+func Unpause(ctx context.Context, nameOrID string) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/bindings/search.go b/pkg/bindings/search.go
deleted file mode 100644
index 0f462357c..000000000
--- a/pkg/bindings/search.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package bindings
-
-import (
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod/image"
-)
-
-type ImageSearchFilters struct {
- Automated bool `json:"automated"`
- Official bool `json:"official"`
- Stars int `json:"stars"`
-}
-
-// TODO This method can be concluded when we determine how we want the filters to work on the
-// API end
-func (i *ImageSearchFilters) ToMapJSON() string {
- return ""
-}
-
-func (c Connection) SearchImages(term string, limit int, filters *ImageSearchFilters) ([]image.SearchResult, error) {
- var (
- searchResults []image.SearchResult
- )
- params := make(map[string]string)
- params["term"] = term
- if limit > 0 {
- params["limit"] = strconv.Itoa(limit)
- }
- if filters != nil {
- params["filters"] = filters.ToMapJSON()
- }
- response, err := c.newRequest(http.MethodGet, "/images/search", nil, params)
- if err != nil {
- return searchResults, nil
- }
- return searchResults, response.Process(&searchResults)
-}
diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go
new file mode 100644
index 000000000..4f2a98f2b
--- /dev/null
+++ b/pkg/bindings/test/common_test.go
@@ -0,0 +1,112 @@
+package test_bindings
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/onsi/ginkgo"
+ "github.com/onsi/gomega/gexec"
+ "github.com/pkg/errors"
+)
+
+const (
+ defaultPodmanBinaryLocation string = "/usr/bin/podman"
+)
+
+type bindingTest struct {
+ artifactDirPath string
+ imageCacheDir string
+ sock string
+ tempDirPath string
+ runRoot string
+ crioRoot string
+}
+
+func (b *bindingTest) runPodman(command []string) *gexec.Session {
+ var cmd []string
+ podmanBinary := defaultPodmanBinaryLocation
+ val, ok := os.LookupEnv("PODMAN_BINARY")
+ if ok {
+ podmanBinary = val
+ }
+ val, ok = os.LookupEnv("CGROUP_MANAGER")
+ if ok {
+ cmd = append(cmd, "--cgroup-manager", val)
+ }
+ val, ok = os.LookupEnv("CNI_CONFIG_DIR")
+ if ok {
+ cmd = append(cmd, "--cni-config-dir", val)
+ }
+ val, ok = os.LookupEnv("CONMON")
+ if ok {
+ cmd = append(cmd, "--conmon", val)
+ }
+ val, ok = os.LookupEnv("ROOT")
+ if ok {
+ cmd = append(cmd, "--root", val)
+ } else {
+ cmd = append(cmd, "--root", b.crioRoot)
+ }
+ val, ok = os.LookupEnv("OCI_RUNTIME")
+ if ok {
+ cmd = append(cmd, "--runtime", val)
+ }
+ val, ok = os.LookupEnv("RUNROOT")
+ if ok {
+ cmd = append(cmd, "--runroot", val)
+ } else {
+ cmd = append(cmd, "--runroot", b.runRoot)
+ }
+ val, ok = os.LookupEnv("STORAGE_DRIVER")
+ if ok {
+ cmd = append(cmd, "--storage-driver", val)
+ }
+ val, ok = os.LookupEnv("STORAGE_OPTIONS")
+ if ok {
+ cmd = append(cmd, "--storage", val)
+ }
+ cmd = append(cmd, command...)
+ c := exec.Command(podmanBinary, cmd...)
+ fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(cmd, " "))
+ session, err := gexec.Start(c, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
+ if err != nil {
+ panic(errors.Errorf("unable to run podman command: %q", cmd))
+ }
+ return session
+}
+
+func newBindingTest() *bindingTest {
+ tmpPath, _ := createTempDirInTempDir()
+ b := bindingTest{
+ crioRoot: filepath.Join(tmpPath, "crio"),
+ runRoot: filepath.Join(tmpPath, "run"),
+ artifactDirPath: "",
+ imageCacheDir: "",
+ sock: fmt.Sprintf("unix:%s", filepath.Join(tmpPath, "api.sock")),
+ tempDirPath: tmpPath,
+ }
+ return &b
+}
+
+// createTempDirinTempDir create a temp dir with prefix podman_test
+func createTempDirInTempDir() (string, error) {
+ return ioutil.TempDir("", "libpod_api")
+}
+
+func (b *bindingTest) startAPIService() *gexec.Session {
+ var (
+ cmd []string
+ )
+ cmd = append(cmd, "--log-level=debug", "service", "--timeout=999999", b.sock)
+ return b.runPodman(cmd)
+}
+
+func (b *bindingTest) cleanup() {
+ if err := os.RemoveAll(b.tempDirPath); err != nil {
+ fmt.Println(err)
+ }
+}
diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go
new file mode 100644
index 000000000..d600197bb
--- /dev/null
+++ b/pkg/bindings/test/images_test.go
@@ -0,0 +1,92 @@
+package test_bindings
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/containers/libpod/pkg/bindings"
+ "github.com/containers/libpod/pkg/bindings/images"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+ "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("Podman images", func() {
+ var (
+ //tempdir string
+ //err error
+ //podmanTest *PodmanTestIntegration
+ bt *bindingTest
+ s *gexec.Session
+ connText context.Context
+ err error
+ false bool
+ //true bool = true
+ )
+
+ BeforeEach(func() {
+ //tempdir, err = CreateTempDirInTempDir()
+ //if err != nil {
+ // os.Exit(1)
+ //}
+ //podmanTest = PodmanTestCreate(tempdir)
+ //podmanTest.Setup()
+ //podmanTest.SeedImages()
+ bt = newBindingTest()
+ p := bt.runPodman([]string{"pull", "docker.io/library/alpine:latest"})
+ p.Wait(45)
+ s = bt.startAPIService()
+ time.Sleep(1 * time.Second)
+ connText, err = bindings.NewConnection(bt.sock)
+ Expect(err).To(BeNil())
+ })
+
+ AfterEach(func() {
+ //podmanTest.Cleanup()
+ //f := CurrentGinkgoTestDescription()
+ //processTestResult(f)
+ s.Kill()
+ bt.cleanup()
+ })
+ It("inspect image", func() {
+ // Inspect invalid image be 404
+ _, err = images.GetImage(connText, "foobar5000", nil)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", 404))
+
+ // Inspect by short name
+ data, err := images.GetImage(connText, "alpine", nil)
+ Expect(err).To(BeNil())
+
+ // Inspect with full ID
+ _, err = images.GetImage(connText, data.ID, nil)
+ Expect(err).To(BeNil())
+
+ // Inspect with partial ID
+ _, err = images.GetImage(connText, data.ID[0:12], nil)
+ Expect(err).To(BeNil())
+ // Inspect by ID
+ // Inspect by long name should work, it doesnt (yet) i think it needs to be html escaped
+ //_, err = images.GetImage(connText, )
+ //Expect(err).To(BeNil())
+ })
+ It("remove image", func() {
+ // Remove invalid image should be a 404
+ _, err = images.RemoveImage(connText, "foobar5000", &false)
+ Expect(err).ToNot(BeNil())
+ code, _ := bindings.CheckResponseCode(err)
+ Expect(code).To(BeNumerically("==", 404))
+
+ _, err := images.GetImage(connText, "alpine", nil)
+ Expect(err).To(BeNil())
+
+ response, err := images.RemoveImage(connText, "alpine", &false)
+ Expect(err).To(BeNil())
+ fmt.Println(response)
+ // to be continued
+
+ })
+
+})
diff --git a/pkg/bindings/volumes.go b/pkg/bindings/volumes.go
deleted file mode 100644
index 219f924e7..000000000
--- a/pkg/bindings/volumes.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package bindings
-
-import (
- "fmt"
- "net/http"
- "strconv"
-
- "github.com/containers/libpod/libpod"
- "github.com/containers/libpod/pkg/api/handlers"
-)
-
-func (c Connection) CreateVolume(config handlers.VolumeCreateConfig) (string, error) {
- var (
- volumeID string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/create", nil, nil)
- if err != nil {
- return volumeID, err
- }
- return volumeID, response.Process(&volumeID)
-}
-
-func (c Connection) InspectVolume(nameOrID string) (*libpod.InspectVolumeData, error) {
- var (
- inspect libpod.InspectVolumeData
- )
- response, err := c.newRequest(http.MethodPost, fmt.Sprintf("/volumes/%s/json", nameOrID), nil, nil)
- if err != nil {
- return &inspect, err
- }
- return &inspect, response.Process(&inspect)
-}
-
-func (c Connection) ListVolumes() error {
- // TODO
- // The API side of things for this one does a lot in main and therefore
- // is not implemented yet.
- return ErrNotImplemented // nolint:typecheck
-}
-
-func (c Connection) PruneVolumes() ([]string, error) {
- var (
- pruned []string
- )
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, nil)
- if err != nil {
- return pruned, err
- }
- return pruned, response.Process(&pruned)
-}
-
-func (c Connection) RemoveVolume(nameOrID string, force bool) error {
- params := make(map[string]string)
- params["force"] = strconv.FormatBool(force)
- response, err := c.newRequest(http.MethodPost, "/volumes/prune", nil, params)
- if err != nil {
- return err
- }
- return response.Process(nil)
-}
diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go
new file mode 100644
index 000000000..05a4f73fd
--- /dev/null
+++ b/pkg/bindings/volumes/volumes.go
@@ -0,0 +1,85 @@
+package volumes
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/api/handlers"
+ "github.com/containers/libpod/pkg/bindings"
+)
+
+// Create creates a volume given its configuration.
+func Create(ctx context.Context, config handlers.VolumeCreateConfig) (string, error) {
+ // TODO This is incomplete. The config needs to be sent via the body
+ var (
+ volumeID string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return "", err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/create", nil)
+ if err != nil {
+ return volumeID, err
+ }
+ return volumeID, response.Process(&volumeID)
+}
+
+// Inspect returns low-level information about a volume.
+func Inspect(ctx context.Context, nameOrID string) (*libpod.InspectVolumeData, error) {
+ var (
+ inspect libpod.InspectVolumeData
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/json", nil, nameOrID)
+ if err != nil {
+ return &inspect, err
+ }
+ return &inspect, response.Process(&inspect)
+}
+
+func List() error {
+ // TODO
+ // The API side of things for this one does a lot in main and therefore
+ // is not implemented yet.
+ return bindings.ErrNotImplemented // nolint:typecheck
+}
+
+// Prune removes unused volumes from the local filesystem.
+func Prune(ctx context.Context) ([]string, error) {
+ var (
+ pruned []string
+ )
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return nil, err
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/prune", nil)
+ if err != nil {
+ return pruned, err
+ }
+ return pruned, response.Process(&pruned)
+}
+
+// Remove deletes the given volume from storage. The optional force parameter
+// is used to remove a volume even if it is being used by a container.
+func Remove(ctx context.Context, nameOrID string, force *bool) error {
+ conn, err := bindings.GetConnectionFromContext(ctx)
+ if err != nil {
+ return err
+ }
+ params := make(map[string]string)
+ if force != nil {
+ params["force"] = strconv.FormatBool(*force)
+ }
+ response, err := conn.DoRequest(nil, http.MethodPost, "/volumes/%s/prune", params, nameOrID)
+ if err != nil {
+ return err
+ }
+ return response.Process(nil)
+}
diff --git a/pkg/inspect/inspect.go b/pkg/inspect/inspect.go
index 8249dc4aa..569f208d9 100644
--- a/pkg/inspect/inspect.go
+++ b/pkg/inspect/inspect.go
@@ -3,6 +3,7 @@ package inspect
import (
"time"
+ "github.com/containers/image/v5/manifest"
"github.com/containers/libpod/libpod/driver"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go/v1"
@@ -10,28 +11,29 @@ import (
// ImageData holds the inspect information of an image
type ImageData struct {
- ID string `json:"Id"`
- Digest digest.Digest `json:"Digest"`
- RepoTags []string `json:"RepoTags"`
- RepoDigests []string `json:"RepoDigests"`
- Parent string `json:"Parent"`
- Comment string `json:"Comment"`
- Created *time.Time `json:"Created"`
- Config *v1.ImageConfig `json:"Config"`
- Version string `json:"Version"`
- Author string `json:"Author"`
- Architecture string `json:"Architecture"`
- Os string `json:"Os"`
- Size int64 `json:"Size"`
- VirtualSize int64 `json:"VirtualSize"`
- GraphDriver *driver.Data `json:"GraphDriver"`
- RootFS *RootFS `json:"RootFS"`
- Labels map[string]string `json:"Labels"`
- Annotations map[string]string `json:"Annotations"`
- ManifestType string `json:"ManifestType"`
- User string `json:"User"`
- History []v1.History `json:"History"`
- NamesHistory []string `json:"NamesHistory"`
+ ID string `json:"Id"`
+ Digest digest.Digest `json:"Digest"`
+ RepoTags []string `json:"RepoTags"`
+ RepoDigests []string `json:"RepoDigests"`
+ Parent string `json:"Parent"`
+ Comment string `json:"Comment"`
+ Created *time.Time `json:"Created"`
+ Config *v1.ImageConfig `json:"Config"`
+ Version string `json:"Version"`
+ Author string `json:"Author"`
+ Architecture string `json:"Architecture"`
+ Os string `json:"Os"`
+ Size int64 `json:"Size"`
+ VirtualSize int64 `json:"VirtualSize"`
+ GraphDriver *driver.Data `json:"GraphDriver"`
+ RootFS *RootFS `json:"RootFS"`
+ Labels map[string]string `json:"Labels"`
+ Annotations map[string]string `json:"Annotations"`
+ ManifestType string `json:"ManifestType"`
+ User string `json:"User"`
+ History []v1.History `json:"History"`
+ NamesHistory []string `json:"NamesHistory"`
+ HealthCheck *manifest.Schema2HealthConfig `json:"Healthcheck,omitempty"`
}
// RootFS holds the root fs information of an image
diff --git a/rootless.md b/rootless.md
index d8997a261..93a2b140f 100644
--- a/rootless.md
+++ b/rootless.md
@@ -44,3 +44,4 @@ can easily fail
* If a build is attempting to use a UID that is not mapped into the user namespace mapping for a container, then builds will not be able to put the UID in an image.
* Making device nodes within a container fails, even when running --privileged.
* The kernel does not allow non root user processes (processes without CAP_MKNOD) to create device nodes. If a container needs to create device nodes, it must be run as root.
+* When using --net=host with rootless containers, subsequent podman execs to that container will not join the host network namespace because it is owned by root.
diff --git a/test/apiv2/00-TEMPLATE b/test/apiv2/00-TEMPLATE
new file mode 100644
index 000000000..e256371ca
--- /dev/null
+++ b/test/apiv2/00-TEMPLATE
@@ -0,0 +1,6 @@
+# -*- sh -*-
+#
+# FIXME: one-line description of the purpose of this file
+#
+
+# vim: filetype=sh
diff --git a/test/apiv2/01-basic.at b/test/apiv2/01-basic.at
new file mode 100644
index 000000000..e87ec534c
--- /dev/null
+++ b/test/apiv2/01-basic.at
@@ -0,0 +1,49 @@
+# -*- sh -*-
+#
+# The earliest most basic tests. If any of these fail, life is bad
+#
+
+# NOTE: paths with a leading slash will be interpreted as-is;
+# paths without will have '/v1.40/' prepended.
+t GET /_ping 200 OK
+t HEAD /_ping 200
+t GET /libpod/_ping 200 OK
+
+for i in /version version; do
+ t GET $i 200 \
+ .Components[0].Name="Podman Engine" \
+ .Components[0].Details.APIVersion=1.40 \
+ .Components[0].Details.MinAPIVersion=1.24 \
+ .Components[0].Details.Os=linux \
+ .ApiVersion=1.40 \
+ .MinAPIVersion=1.24 \
+ .Os=linux
+done
+
+#
+# Garbage tests - requests that should yield errors
+#
+t GET /nonesuch 404
+t POST /nonesuch '' 404
+t GET container/nonesuch/json 404
+t GET libpod/containers/nonesuch/json 404
+t GET 'libpod/containers/json?a=b' 400
+
+# Method not allowed
+t POST /_ping '' 405
+t DELETE /_ping 405
+t POST libpod/containers/json '' 405
+t POST libpod/pods/abc '' 405
+t POST info '' 405
+t GET libpod/containers/create 405
+
+#
+# system info
+#
+# FIXME: run 'podman info --format=json', and compare select fields
+t GET info 200 \
+ .OSType=linux \
+ .DefaultRuntime=runc \
+ .MemTotal~[0-9]\\+
+
+# vim: filetype=sh
diff --git a/test/apiv2/10-images.at b/test/apiv2/10-images.at
new file mode 100644
index 000000000..243b35e9f
--- /dev/null
+++ b/test/apiv2/10-images.at
@@ -0,0 +1,36 @@
+# -*- sh -*-
+#
+# Tests for image-related endpoints
+#
+
+# FIXME: API doesn't support pull yet, so use podman
+podman pull -q $IMAGE
+
+# We want the SHA without the "sha256:" prefix
+full_iid=$(podman images --no-trunc --format '{{.ID}}' $IMAGE)
+iid=${full_iid##sha256:}
+
+t GET libpod/images/$iid/exists 204
+t GET libpod/images/$PODMAN_TEST_IMAGE_NAME/exists 204
+
+# FIXME: compare to actual podman info
+t GET libpod/images/json 200 \
+ .[0].Id=${iid}
+
+t GET libpod/images/$iid/json 200 \
+ .Id=$iid \
+ .RepoTags[0]=$IMAGE
+
+# Same thing, but with abbreviated image id
+t GET libpod/images/${iid:0:12}/json 200 \
+ .Id=$iid \
+ .RepoTags[0]=$IMAGE
+
+# FIXME: docker API incompatibility: libpod returns 'id', docker 'sha256:id'
+t GET images/$iid/json 200 \
+ .Id=sha256:$iid \
+ .RepoTags[0]=$IMAGE
+
+#t POST images/create fromImage=alpine 201 foo
+
+# vim: filetype=sh
diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at
new file mode 100644
index 000000000..5f0a145f0
--- /dev/null
+++ b/test/apiv2/20-containers.at
@@ -0,0 +1,29 @@
+# -*- sh -*-
+#
+# test container-related endpoints
+#
+
+podman pull $IMAGE &>/dev/null
+
+# Unimplemented
+#t POST libpod/containers/create '' 201 'sdf'
+
+# Ensure clean slate
+podman rm -a -f &>/dev/null
+
+t GET libpod/containers/json 200 []
+
+podman run $IMAGE true
+
+t GET libpod/containers/json 200 \
+ .[0].ID~[0-9a-f]\\{12\\} \
+ .[0].Image=$IMAGE \
+ .[0].Command=true \
+ .[0].State=4 \
+ .[0].IsInfra=false
+
+cid=$(jq -r '.[0].ID' <<<"$output")
+
+t DELETE libpod/containers/$cid 204
+
+# vim: filetype=sh
diff --git a/test/apiv2/30-volumes.at b/test/apiv2/30-volumes.at
new file mode 100644
index 000000000..b599680e3
--- /dev/null
+++ b/test/apiv2/30-volumes.at
@@ -0,0 +1,14 @@
+# -*- sh -*-
+#
+# volume-related tests
+#
+
+#
+# FIXME: endpoints seem to be unimplemented, return 404
+#
+if false; then
+t GET libpod/volumes/json 200 null
+t POST libpod/volumes/create name=foo 201
+fi
+
+# vim: filetype=sh
diff --git a/test/apiv2/40-pods.at b/test/apiv2/40-pods.at
new file mode 100644
index 000000000..1c25a3822
--- /dev/null
+++ b/test/apiv2/40-pods.at
@@ -0,0 +1,33 @@
+# -*- sh -*-
+#
+# test pod-related endpoints
+#
+
+t GET libpod/pods/json 200 null
+t POST libpod/pods/create name=foo 201 '{"id":"machine.slice"}' # FIXME!
+t GET libpod/pods/foo/exists 204
+t GET libpod/pods/notfoo/exists 404
+t GET libpod/pods/foo/json 200 .Config.name=foo .Containers=null
+t GET libpod/pods/json 200 .[0].Config.name=foo .[0].Containers=null
+
+# Cannot create a dup pod with the same name (FIXME: should that be 409?)
+t POST libpod/pods/create name=foo 500 .cause="pod already exists"
+
+#t POST libpod/pods/create a=b 400 .cause='bad parameter' # FIXME: unimplemented
+
+t POST libpod/pods/foo/pause '' 204
+t POST libpod/pods/foo/unpause '' 200
+t POST libpod/pods/foo/unpause '' 200 # (2nd time)
+t POST libpod/pods/foo/stop '' 304
+t POST libpod/pods/foo/restart '' 500 .cause="no such container"
+
+t POST libpod/pods/bar/restart '' 404
+
+#t POST libpod/pods/prune '' 200 # FIXME: unimplemented, returns 500
+#t POST libpod/pods/prune 'a=b' 400 # FIXME: unimplemented, returns 500
+
+# Clean up; and try twice, making sure that the second time fails
+t DELETE libpod/pods/foo 204
+t DELETE libpod/pods/foo 404
+
+# vim: filetype=sh
diff --git a/test/apiv2/README.md b/test/apiv2/README.md
new file mode 100644
index 000000000..252d6454e
--- /dev/null
+++ b/test/apiv2/README.md
@@ -0,0 +1,63 @@
+API v2 tests
+============
+
+This directory contains tests for the podman version 2 API (HTTP).
+
+Tests themselves are in files of the form 'NN-NAME.at' where NN is a
+two-digit number, NAME is a descriptive name, and '.at' is just
+an extension I picked.
+
+Running Tests
+=============
+
+The main test runner is `test-apiv2`. Usage is:
+
+ $ sudo ./test-apiv2 [NAME [...]]
+
+...where NAME is one or more optional test names, e.g. 'image' or 'pod'
+or both. By default, `test-apiv2` will invoke all `*.at` tests.
+
+`test-apiv2` connects to *localhost only* and *via TCP*. There is
+no support here for remote hosts or for UNIX sockets. This is a
+framework for testing the API, not all possible protocols.
+
+`test-apiv2` will start the service if it isn't already running.
+
+
+Writing Tests
+=============
+
+The main test function is `t`. It runs `curl` against the server,
+with POST parameters if present, and compares return status and
+(optionally) string results from the server:
+
+ t GET /_ping 200 OK
+ ^^^ ^^^^^^ ^^^ ^^
+ | | | +--- expected string result
+ | | +------- expected return code
+ | +-------------- endpoint to access
+ +------------------ method (GET, POST, DELETE, HEAD)
+
+
+ t POST libpod/volumes/create name=foo 201 .ID~[0-9a-f]\\{12\\}
+ ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^
+ | | | JSON '.ID': expect 12-char hex
+ | | +-- expected code
+ | +----------- POST params
+ +--------------------------------- note the missing slash
+
+Notes:
+
+* If the endpoint has a leading slash (`/_ping`), `t` leaves it unchanged.
+If there's no leading slash, `t` prepends `/v1.40`. This is a simple
+convenience for simplicity of writing tests.
+
+* When method is POST, the argument after the endpoint must be a series
+of POST arguments in the form 'key=value', separated by commas. `t` will
+convert those to JSON form for passing to the server.
+
+* The final arguments are one or more expected string results. If an
+argument starts with a dot, `t` will invoke `jq` on the output to
+fetch that field, and will compare it to the right-hand side of
+the argument. If the separator is `=` (equals), `t` will require
+an exact match; if `~` (tilde), `t` will use `expr` to compare.
diff --git a/test/apiv2/test-apiv2 b/test/apiv2/test-apiv2
new file mode 100755
index 000000000..786c976d6
--- /dev/null
+++ b/test/apiv2/test-apiv2
@@ -0,0 +1,325 @@
+#!/bin/bash
+#
+# Usage: test-apiv2 [PORT]
+#
+# DEVELOPER NOTE: you almost certainly don't need to play in here. See README.
+#
+ME=$(basename $0)
+
+###############################################################################
+# BEGIN stuff you can but probably shouldn't customize
+
+PODMAN_TEST_IMAGE_REGISTRY=${PODMAN_TEST_IMAGE_REGISTRY:-"quay.io"}
+PODMAN_TEST_IMAGE_USER=${PODMAN_TEST_IMAGE_USER:-"libpod"}
+PODMAN_TEST_IMAGE_NAME=${PODMAN_TEST_IMAGE_NAME:-"alpine_labels"}
+PODMAN_TEST_IMAGE_TAG=${PODMAN_TEST_IMAGE_TAG:-"latest"}
+PODMAN_TEST_IMAGE_FQN="$PODMAN_TEST_IMAGE_REGISTRY/$PODMAN_TEST_IMAGE_USER/$PODMAN_TEST_IMAGE_NAME:$PODMAN_TEST_IMAGE_TAG"
+
+IMAGE=$PODMAN_TEST_IMAGE_FQN
+
+# END stuff you can but probably shouldn't customize
+###############################################################################
+# BEGIN setup
+
+TMPDIR=${TMPDIR:-/tmp}
+WORKDIR=$(mktemp --tmpdir -d $ME.tmp.XXXXXX)
+
+# Log of all HTTP requests and responses
+LOG=${TMPDIR}/$ME.log.$(date +'%Y%m%dT%H%M%S')
+
+HOST=localhost
+PORT=${PODMAN_SERVICE_PORT:-8081}
+
+# Keep track of test count and failures in files, not variables, because
+# variables don't carry back up from subshells.
+testcounter_file=$WORKDIR/.testcounter
+failures_file=$WORKDIR/.failures
+
+echo 0 >$testcounter_file
+echo 0 >$failures_file
+
+# Where the tests live
+TESTS_DIR=$(realpath $(dirname $0))
+
+# END setup
+###############################################################################
+# BEGIN infrastructure code - the helper functions used in tests themselves
+
+#########
+# die # Exit error with a message to stderr
+#########
+function die() {
+ echo "$ME: $*" >&2
+ exit 1
+}
+
+########
+# is # Simple comparison
+########
+function is() {
+ local actual=$1
+ local expect=$2
+ local testname=$3
+
+ if [ "$actual" = "$expect" ]; then
+ # On success, include expected value; this helps readers understand
+ _show_ok 1 "$testname=$expect"
+ return
+ fi
+ _show_ok 0 "$testname" "$expect" "$actual"
+}
+
+##########
+# like # Compare, but allowing patterns
+##########
+function like() {
+ local actual=$1
+ local expect=$2
+ local testname=$3
+
+ if expr "$actual" : "$expect" &>/dev/null; then
+ # On success, include expected value; this helps readers understand
+ _show_ok 1 "$testname~$expect"
+ return
+ fi
+ _show_ok 0 "$testname" "~ $expect" "$actual"
+}
+
+##############
+# _show_ok # Helper for is() and like(): displays 'ok' or 'not ok'
+##############
+function _show_ok() {
+ local ok=$1
+ local testname=$2
+
+ # If output is a tty, colorize pass/fail
+ local red=
+ local green=
+ local reset=
+ local bold=
+ if [ -t 3 ]; then
+ red='\e[31m'
+ green='\e[32m'
+ reset='\e[0m'
+ bold='\e[1m'
+ fi
+
+ _bump $testcounter_file
+ count=$(<$testcounter_file)
+ if [ $ok -eq 1 ]; then
+ echo -e "${green}ok $count $testname${reset}" >&3
+ return
+ fi
+
+ # Failed
+ local expect=$3
+ local actual=$4
+ echo -e "${red}not ok $count $testname${reset}" >&3
+ echo -e "${red}# expected: $expect${reset}" >&3
+ echo -e "${red}# actual: ${bold}$actual${reset}" >&3
+
+ _bump $failures_file
+}
+
+###########
+# _bump # Increment a counter in a file
+###########
+function _bump() {
+ local file=$1
+
+ count=$(<$file)
+ echo $(( $count + 1 )) >| $file
+}
+
+#############
+# jsonify # convert 'foo=bar,x=y' to json {"foo":"bar","x":"y"}
+#############
+function jsonify() {
+ # split by comma
+ local -a settings_in
+ read -ra settings_in <<<"$1"
+
+ # convert each to double-quoted form
+ local -a settings_out
+ for i in ${settings_in[*]}; do
+ settings_out+=$(sed -e 's/\(.*\)=\(.*\)/"\1":"\2"/' <<<$i)
+ done
+
+ # ...and wrap inside braces.
+ # FIXME: handle commas
+ echo "{${settings_out[*]}}"
+}
+
+#######
+# t # Main test helper
+#######
+function t() {
+ local method=$1; shift
+ local path=$1; shift
+ local curl_args
+
+ local testname="$method $path"
+ # POST requests require an extra params arg
+ if [[ $method = "POST" ]]; then
+ curl_args="-d $(jsonify $1)"
+ testname="$testname [$1]"
+ shift
+ fi
+ # curl -X HEAD but without --head seems to wait for output anyway
+ if [[ $method == "HEAD" ]]; then
+ curl_args="--head"
+ fi
+ local expected_code=$1; shift
+
+ # If given path begins with /, use it as-is; otherwise prepend /version/
+ local url=http://$HOST:$PORT
+ if expr "$path" : "/" >/dev/null; then
+ url="$url$path"
+ else
+ url="$url/v1.40/$path"
+ fi
+
+ # Log every action we do
+ echo "-------------------------------------------------------------" >>$LOG
+ echo "\$ $testname" >>$LOG
+ rm -f $WORKDIR/curl.*
+ curl -s -X $method ${curl_args} \
+ -H 'Content-type: application/json' \
+ --dump-header $WORKDIR/curl.headers.out \
+ -o $WORKDIR/curl.result.out "$url"
+
+ if [[ $? -eq 7 ]]; then
+ echo "FATAL: curl failure on $url - cannot continue" >&2
+ exit 1
+ fi
+
+ cat $WORKDIR/curl.headers.out $WORKDIR/curl.result.out >>$LOG 2>/dev/null || true
+
+ # Test return code
+ actual_code=$(head -n1 $WORKDIR/curl.headers.out | awk '/^HTTP/ { print $2}')
+ is "$actual_code" "$expected_code" "$testname : status"
+
+ output=$(< $WORKDIR/curl.result.out)
+
+ for i; do
+ case "$i" in
+ # Exact match on json field
+ .*=*)
+ json_field=$(expr "$i" : "\([^=]*\)=")
+ expect=$(expr "$i" : '[^=]*=\(.*\)')
+ actual=$(jq -r "$json_field" <<<"$output")
+ is "$actual" "$expect" "$testname : $json_field"
+ ;;
+ # regex match on json field
+ .*~*)
+ json_field=$(expr "$i" : "\([^~]*\)~")
+ expect=$(expr "$i" : '[^~]*~\(.*\)')
+ actual=$(jq -r "$json_field" <<<"$output")
+ like "$actual" "$expect" "$testname : $json_field"
+ ;;
+ # Direct string comparison
+ *)
+ is "$output" "$i" "$testname : output"
+ ;;
+ esac
+ done
+}
+
+###################
+# start_service # Run the socket listener
+###################
+service_pid=
+function start_service() {
+ # If there's a listener on the port, nothing for us to do
+ echo -n >/dev/tcp/$HOST/$PORT &>/dev/null && return
+
+ if [ "$HOST" != "localhost" ]; then
+ die "Cannot start service on non-localhost ($HOST)"
+ fi
+
+ if [ $(id -u) -ne 0 ]; then
+ echo "$ME: WARNING: running service rootless is unlikely to work!" >&2
+ fi
+
+ # Find the binary
+ SERVICE_BIN=${SERVICE_BIN:-${TESTS_DIR}/../../bin/service}
+ test -x $SERVICE_BIN || die "Not found: $SERVICE_BIN"
+
+ systemd-socket-activate -l 127.0.0.1:$PORT \
+ $SERVICE_BIN --root $WORKDIR/root \
+ &> $WORKDIR/server.log &
+ service_pid=$!
+
+ # Wait
+ local _timeout=5
+ while [ $_timeout -gt 0 ]; do
+ echo -n >/dev/tcp/$HOST/$PORT &>/dev/null && return
+ sleep 1
+ _timeout=$(( $_timeout - 1 ))
+ done
+ die "Timed out waiting for service"
+}
+
+# END infrastructure code
+###############################################################################
+# BEGIN sanity checks
+
+for tool in curl jq podman; do
+ type $tool &>/dev/null || die "$ME: Required tool '$tool' not found"
+done
+
+# END sanity checks
+###############################################################################
+# BEGIN entry handler (subtest invoker)
+
+# Identify the tests to run. If called with args, use those as globs.
+tests_to_run=()
+if [ -n "$*" ]; then
+ shopt -s nullglob
+ for i; do
+ match=(${TESTS_DIR}/*${i}*.at)
+ if [ ${#match} -eq 0 ]; then
+ die "No match for $TESTS_DIR/*$i*.at"
+ fi
+ tests_to_run+=("${match[@]}")
+ done
+ shopt -u nullglob
+else
+ tests_to_run=($TESTS_DIR/*.at)
+fi
+
+# Because subtests may run podman or other commands that emit stderr;
+# redirect all those and use fd 3 for all output
+exec 3>&1 &>$WORKDIR/output.log
+
+start_service
+
+for i in ${tests_to_run[@]}; do
+ source $i
+done
+
+# END entry handler
+###############################################################################
+
+# Clean up
+
+if [ -n "$service_pid" ]; then
+ # Yep, has to be -9. It ignores everything else.
+ kill -9 $service_pid
+fi
+
+test_count=$(<$testcounter_file)
+failure_count=$(<$failures_file)
+
+if [ $failure_count -gt 0 -a -s "$WORKDIR/output.log" ]; then
+ echo "# Collected stdout/stderr:" >&3
+ sed -e 's/^/# /' < $WORKDIR/output.log >&3
+fi
+
+if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
+ rm -rf $WORKDIR
+fi
+
+echo "1..${test_count}" >&3
+
+exit $failure_count
diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go
index 9d23384ea..ebac087ac 100644
--- a/test/e2e/inspect_test.go
+++ b/test/e2e/inspect_test.go
@@ -164,4 +164,17 @@ var _ = Describe("Podman inspect", func() {
Expect(inspectDst.ExitCode()).To(Equal(0))
Expect(inspectDst.OutputToString()).To(Equal("/test1"))
})
+
+ It("podman inspect shows healthcheck on docker image", func() {
+ pull := podmanTest.Podman([]string{"pull", healthcheck})
+ pull.WaitWithDefaultTimeout()
+ Expect(pull.ExitCode()).To(BeZero())
+
+ session := podmanTest.Podman([]string{"inspect", "--format=json", healthcheck})
+ session.WaitWithDefaultTimeout()
+ imageData := session.InspectImageJSON()
+ Expect(imageData[0].HealthCheck.Timeout).To(BeNumerically("==", 3000000000))
+ Expect(imageData[0].HealthCheck.Interval).To(BeNumerically("==", 60000000000))
+ Expect(imageData[0].HealthCheck.Test).To(Equal([]string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}))
+ })
})
diff --git a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
index 2d4e9f890..9fcfd0867 100644
--- a/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
+++ b/vendor/github.com/opencontainers/selinux/go-selinux/selinux_linux.go
@@ -7,7 +7,6 @@ import (
"bytes"
"crypto/rand"
"encoding/binary"
- "errors"
"fmt"
"io"
"io/ioutil"
@@ -18,6 +17,8 @@ import (
"strings"
"sync"
"syscall"
+
+ "github.com/pkg/errors"
"golang.org/x/sys/unix"
)
@@ -253,6 +254,12 @@ func getSELinuxPolicyRoot() string {
return filepath.Join(selinuxDir, readConfig(selinuxTypeTag))
}
+func isProcHandle(fh *os.File) (bool, error) {
+ var buf unix.Statfs_t
+ err := unix.Fstatfs(int(fh.Fd()), &buf)
+ return buf.Type == unix.PROC_SUPER_MAGIC, err
+}
+
func readCon(fpath string) (string, error) {
if fpath == "" {
return "", ErrEmptyPath
@@ -264,6 +271,12 @@ func readCon(fpath string) (string, error) {
}
defer in.Close()
+ if ok, err := isProcHandle(in); err != nil {
+ return "", err
+ } else if !ok {
+ return "", fmt.Errorf("%s not on procfs", fpath)
+ }
+
var retval string
if _, err := fmt.Fscanf(in, "%s", &retval); err != nil {
return "", err
@@ -276,7 +289,10 @@ func SetFileLabel(fpath string, label string) error {
if fpath == "" {
return ErrEmptyPath
}
- return lsetxattr(fpath, xattrNameSelinux, []byte(label), 0)
+ if err := lsetxattr(fpath, xattrNameSelinux, []byte(label), 0); err != nil {
+ return errors.Wrapf(err, "failed to set file label on %s", fpath)
+ }
+ return nil
}
// FileLabel returns the SELinux label for this path or returns an error.
@@ -346,12 +362,21 @@ func writeCon(fpath string, val string) error {
}
defer out.Close()
+ if ok, err := isProcHandle(out); err != nil {
+ return err
+ } else if !ok {
+ return fmt.Errorf("%s not on procfs", fpath)
+ }
+
if val != "" {
_, err = out.Write([]byte(val))
} else {
_, err = out.Write(nil)
}
- return err
+ if err != nil {
+ return errors.Wrapf(err, "failed to set %s on procfs", fpath)
+ }
+ return nil
}
/*
@@ -394,7 +419,7 @@ func SetExecLabel(label string) error {
}
/*
-SetTaskLabel sets the SELinux label for the current thread, or an error.
+SetTaskLabel sets the SELinux label for the current thread, or an error.
This requires the dyntransition permission.
*/
func SetTaskLabel(label string) error {
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6385ab250..4d96788a8 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -403,7 +403,7 @@ github.com/opencontainers/runtime-tools/generate
github.com/opencontainers/runtime-tools/generate/seccomp
github.com/opencontainers/runtime-tools/specerror
github.com/opencontainers/runtime-tools/validate
-# github.com/opencontainers/selinux v1.3.0
+# github.com/opencontainers/selinux v1.3.1
github.com/opencontainers/selinux/go-selinux
github.com/opencontainers/selinux/go-selinux/label
# github.com/openshift/api v0.0.0-20200106203948-7ab22a2c8316
@@ -444,7 +444,7 @@ github.com/prometheus/common/model
github.com/prometheus/procfs
github.com/prometheus/procfs/internal/fs
github.com/prometheus/procfs/internal/util
-# github.com/rootless-containers/rootlesskit v0.7.1
+# github.com/rootless-containers/rootlesskit v0.7.2
github.com/rootless-containers/rootlesskit/pkg/msgutil
github.com/rootless-containers/rootlesskit/pkg/port
github.com/rootless-containers/rootlesskit/pkg/port/builtin