summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--cmd/podman/push.go5
-rw-r--r--contrib/systemd/system/podman-docker.conf1
-rw-r--r--docs/source/markdown/podman-container-exists.1.md4
-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-inspect.1.md2
-rw-r--r--docs/source/markdown/podman-rm.1.md22
-rw-r--r--docs/source/markdown/podman-rmi.1.md8
-rw-r--r--docs/source/markdown/podman-system-prune.1.md6
-rw-r--r--install.md12
-rw-r--r--libpod/image/image.go9
-rw-r--r--pkg/inspect/inspect.go46
-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
22 files changed, 648 insertions, 53 deletions
diff --git a/Makefile b/Makefile
index a7e779dd2..da58d2e8a 100644
--- a/Makefile
+++ b/Makefile
@@ -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-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-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-inspect.1.md b/docs/source/markdown/podman-inspect.1.md
index ad4d0659e..4998772c3 100644
--- a/docs/source/markdown/podman-inspect.1.md
+++ b/docs/source/markdown/podman-inspect.1.md
@@ -36,7 +36,7 @@ The latest option is not supported on the remote client or when invoked as *podm
**--size**, **-s**
-Display the total file size if the type is a container
+In addition to normal output, display the total file size if the type is a container.
## EXAMPLE
diff --git a/docs/source/markdown/podman-rm.1.md b/docs/source/markdown/podman-rm.1.md
index f6dac8db4..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,44 +46,44 @@ 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
diff --git a/docs/source/markdown/podman-rmi.1.md b/docs/source/markdown/podman-rmi.1.md
index 0f5af3a45..78ef2b157 100644
--- a/docs/source/markdown/podman-rmi.1.md
+++ b/docs/source/markdown/podman-rmi.1.md
@@ -24,21 +24,21 @@ 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** All specified images removed
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/install.md b/install.md
index 90ad4f233..05cbce7c9 100644
--- a/install.md
+++ b/install.md
@@ -68,6 +68,16 @@ Using [Homebrew](https://brew.sh/):
brew cask install podman
```
+#### [OpenEmbedded](https://www.openembedded.org)
+
+Bitbake recipes for podman and its dependencies are available in the
+[meta-virtualization layer](https://git.yoctoproject.org/cgit/cgit.cgi/meta-virtualization/).
+Add the layer to your OpenEmbedded build environment and build podman using:
+
+```bash
+bitbake podman
+```
+
#### [openSUSE](https://www.opensuse.org)
```bash
@@ -177,6 +187,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)
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/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/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"}))
+ })
})