summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xAPI.md10
-rw-r--r--cmd/podman/cliconfig/config.go4
-rw-r--r--cmd/podman/cliconfig/create.go1
-rw-r--r--cmd/podman/commands.go16
-rw-r--r--cmd/podman/commands_remoteclient.go10
-rw-r--r--cmd/podman/cp.go21
-rw-r--r--cmd/podman/create.go25
-rw-r--r--cmd/podman/healthcheck.go25
-rw-r--r--cmd/podman/healthcheck_run.go53
-rw-r--r--cmd/podman/main.go1
-rw-r--r--cmd/podman/pod.go3
-rw-r--r--cmd/podman/pod_stats.go47
-rw-r--r--cmd/podman/pod_top.go31
-rw-r--r--cmd/podman/varlink/io.podman.varlink15
-rw-r--r--cmd/podman/wait.go53
-rw-r--r--completions/bash/podman52
-rw-r--r--docs/podman-cp.1.md7
-rw-r--r--docs/podman-healthcheck-run.1.md39
-rw-r--r--docs/podman-healthcheck.1.md22
-rw-r--r--libpod/boltdb_state.go167
-rw-r--r--libpod/container.go15
-rw-r--r--libpod/container_internal.go20
-rw-r--r--libpod/container_internal_test.go48
-rw-r--r--libpod/healthcheck.go92
-rw-r--r--libpod/image/image.go29
-rw-r--r--libpod/oci.go6
-rw-r--r--libpod/options.go12
-rw-r--r--libpod/runtime_pod_linux.go5
-rw-r--r--pkg/adapter/containers.go24
-rw-r--r--pkg/adapter/containers_remote.go26
-rw-r--r--pkg/adapter/pods.go60
-rw-r--r--pkg/adapter/pods_remote.go108
-rw-r--r--pkg/adapter/runtime.go5
-rw-r--r--pkg/adapter/runtime_remote.go5
-rw-r--r--pkg/spec/config_linux.go25
-rw-r--r--pkg/spec/createconfig.go7
-rw-r--r--pkg/varlinkapi/containers.go56
-rw-r--r--pkg/varlinkapi/pods.go31
-rw-r--r--test/e2e/config.go1
-rw-r--r--test/e2e/config_amd64.go2
-rw-r--r--test/e2e/healthcheck_run_test.go85
-rw-r--r--test/e2e/run_device_test.go8
-rw-r--r--transfer.md1
43 files changed, 1075 insertions, 198 deletions
diff --git a/API.md b/API.md
index d255037c1..fc0361195 100755
--- a/API.md
+++ b/API.md
@@ -143,7 +143,7 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func VolumesPrune() []string, []string](#VolumesPrune)
-[func WaitContainer(name: string) int](#WaitContainer)
+[func WaitContainer(name: string, interval: int) int](#WaitContainer)
[type BuildInfo](#BuildInfo)
@@ -1013,10 +1013,10 @@ VolumesPrune removes unused volumes on the host
### <a name="WaitContainer"></a>func WaitContainer
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
-method WaitContainer(name: [string](https://godoc.org/builtin#string)) [int](https://godoc.org/builtin#int)</div>
-WaitContainer takes the name or ID of a container and waits until the container stops. Upon stopping, the return
-code of the container is returned. If the container container cannot be found by ID or name,
-a [ContainerNotFound](#ContainerNotFound) error is returned.
+method WaitContainer(name: [string](https://godoc.org/builtin#string), interval: [int](https://godoc.org/builtin#int)) [int](https://godoc.org/builtin#int)</div>
+WaitContainer takes the name or ID of a container and waits the given interval in milliseconds until the container
+stops. Upon stopping, the return code of the container is returned. If the container container cannot be found by ID
+or name, a [ContainerNotFound](#ContainerNotFound) error is returned.
## Types
### <a name="BuildInfo"></a>type BuildInfo
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go
index ea99aafc2..7945cb6cb 100644
--- a/cmd/podman/cliconfig/config.go
+++ b/cmd/podman/cliconfig/config.go
@@ -217,6 +217,10 @@ type PauseValues struct {
All bool
}
+type HealthCheckValues struct {
+ PodmanCommand
+}
+
type KubePlayValues struct {
PodmanCommand
Authfile string
diff --git a/cmd/podman/cliconfig/create.go b/cmd/podman/cliconfig/create.go
index b5ca1be9c..49ab3d827 100644
--- a/cmd/podman/cliconfig/create.go
+++ b/cmd/podman/cliconfig/create.go
@@ -23,4 +23,5 @@ type BuildValues struct {
type CpValues struct {
PodmanCommand
+ Extract bool
}
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go
index c261e54e2..d37af70c1 100644
--- a/cmd/podman/commands.go
+++ b/cmd/podman/commands.go
@@ -35,7 +35,6 @@ func getMainCommands() []*cobra.Command {
_topCommand,
_umountCommand,
_unpauseCommand,
- _waitCommand,
}
if len(_varlinkCommand.Use) > 0 {
@@ -85,14 +84,6 @@ func getContainerSubCommands() []*cobra.Command {
}
}
-// Commands that the local client implements
-func getPodSubCommands() []*cobra.Command {
- return []*cobra.Command{
- _podStatsCommand,
- _podTopCommand,
- }
-}
-
func getGenerateSubCommands() []*cobra.Command {
return []*cobra.Command{
_containerKubeCommand,
@@ -121,3 +112,10 @@ func getSystemSubCommands() []*cobra.Command {
_renumberCommand,
}
}
+
+// Commands that the local client implements
+func getHealtcheckSubCommands() []*cobra.Command {
+ return []*cobra.Command{
+ _healthcheckrunCommand,
+ }
+}
diff --git a/cmd/podman/commands_remoteclient.go b/cmd/podman/commands_remoteclient.go
index 081043b25..9b09e7dbc 100644
--- a/cmd/podman/commands_remoteclient.go
+++ b/cmd/podman/commands_remoteclient.go
@@ -29,11 +29,6 @@ func getContainerSubCommands() []*cobra.Command {
}
// commands that only the remoteclient implements
-func getPodSubCommands() []*cobra.Command {
- return []*cobra.Command{}
-}
-
-// commands that only the remoteclient implements
func getGenerateSubCommands() []*cobra.Command {
return []*cobra.Command{}
}
@@ -52,3 +47,8 @@ func getTrustSubCommands() []*cobra.Command {
func getSystemSubCommands() []*cobra.Command {
return []*cobra.Command{}
}
+
+// Commands that the remoteclient implements
+func getHealtcheckSubCommands() []*cobra.Command {
+ return []*cobra.Command{}
+}
diff --git a/cmd/podman/cp.go b/cmd/podman/cp.go
index 5958370b6..a3411b158 100644
--- a/cmd/podman/cp.go
+++ b/cmd/podman/cp.go
@@ -43,6 +43,8 @@ var (
func init() {
cpCommand.Command = _cpCommand
+ flags := cpCommand.Flags()
+ flags.BoolVar(&cpCommand.Extract, "extract", false, "Extract the tar file into the destination directory.")
rootCmd.AddCommand(cpCommand.Command)
}
@@ -61,10 +63,11 @@ func cpCmd(c *cliconfig.CpValues) error {
}
defer runtime.Shutdown(false)
- return copyBetweenHostAndContainer(runtime, args[0], args[1])
+ extract := c.Flag("extract").Changed
+ return copyBetweenHostAndContainer(runtime, args[0], args[1], extract)
}
-func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string) error {
+func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest string, extract bool) error {
srcCtr, srcPath := parsePath(runtime, src)
destCtr, destPath := parsePath(runtime, dest)
@@ -166,7 +169,7 @@ func copyBetweenHostAndContainer(runtime *libpod.Runtime, src string, dest strin
var lastError error
for _, src := range glob {
- err := copy(src, destPath, dest, idMappingOpts, &containerOwner)
+ err := copy(src, destPath, dest, idMappingOpts, &containerOwner, extract)
if lastError != nil {
logrus.Error(lastError)
}
@@ -219,7 +222,7 @@ func getPathInfo(path string) (string, os.FileInfo, error) {
return path, srcfi, nil
}
-func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair) error {
+func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, chownOpts *idtools.IDPair, extract bool) error {
srcPath, err := filepath.EvalSymlinks(src)
if err != nil {
return errors.Wrapf(err, "error evaluating symlinks %q", srcPath)
@@ -240,6 +243,7 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch
// return functions for copying items
copyFileWithTar := chrootarchive.CopyFileWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
copyWithTar := chrootarchive.CopyWithTarAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
+ untarPath := chrootarchive.UntarPathAndChown(chownOpts, digest.Canonical.Digester().Hash(), idMappingOpts.UIDMap, idMappingOpts.GIDMap)
if srcfi.IsDir() {
@@ -263,6 +267,15 @@ func copy(src, destPath, dest string, idMappingOpts storage.IDMappingOptions, ch
destPath = filepath.Join(destPath, filepath.Base(srcPath))
}
}
+
+ if extract {
+ // We're extracting an archive into the destination directory.
+ logrus.Debugf("extracting contents of %q into %q", srcPath, destPath)
+ if err = untarPath(srcPath, destPath); err != nil {
+ return errors.Wrapf(err, "error extracting %q into %q", srcPath, destPath)
+ }
+ return nil
+ }
// Copy the file, preserving attributes.
logrus.Debugf("copying %q to %q", srcPath, destPath)
if err = copyFileWithTar(srcPath, destPath); err != nil {
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index 129c886b2..a7b9bbf31 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -12,6 +12,7 @@ import (
"strings"
"syscall"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
@@ -117,6 +118,10 @@ func createInit(c *cliconfig.PodmanCommand) error {
}
func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
+ var (
+ hasHealthCheck bool
+ healthCheck *manifest.Schema2HealthConfig
+ )
if c.Bool("trace") {
span, _ := opentracing.StartSpanFromContext(Ctx, "createContainer")
defer span.Finish()
@@ -163,12 +168,32 @@ func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libp
} else {
imageName = newImage.ID()
}
+
+ // add healthcheck if it exists AND is correct mediatype
+ _, mediaType, err := newImage.Manifest(ctx)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID())
+ }
+ if mediaType == manifest.DockerV2Schema2MediaType {
+ healthCheck, err = newImage.GetHealthCheck(ctx)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0])
+ }
+ if healthCheck != nil {
+ hasHealthCheck = true
+ }
+ }
}
createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data)
if err != nil {
return nil, nil, err
}
+ // Because parseCreateOpts does derive anything from the image, we add health check
+ // at this point. The rest is done by WithOptions.
+ createConfig.HasHealthCheck = hasHealthCheck
+ createConfig.HealthCheck = healthCheck
+
ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, nil)
if err != nil {
return nil, nil, err
diff --git a/cmd/podman/healthcheck.go b/cmd/podman/healthcheck.go
new file mode 100644
index 000000000..e7cc125cc
--- /dev/null
+++ b/cmd/podman/healthcheck.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/spf13/cobra"
+)
+
+var healthcheckDescription = "Manage health checks on containers"
+var healthcheckCommand = cliconfig.PodmanCommand{
+ Command: &cobra.Command{
+ Use: "healthcheck",
+ Short: "Manage Healthcheck",
+ Long: healthcheckDescription,
+ },
+}
+
+// Commands that are universally implemented
+var healthcheckCommands []*cobra.Command
+
+func init() {
+ healthcheckCommand.AddCommand(healthcheckCommands...)
+ healthcheckCommand.AddCommand(getHealtcheckSubCommands()...)
+ healthcheckCommand.SetUsageTemplate(UsageTemplate())
+ rootCmd.AddCommand(healthcheckCommand.Command)
+}
diff --git a/cmd/podman/healthcheck_run.go b/cmd/podman/healthcheck_run.go
new file mode 100644
index 000000000..d92b2ac01
--- /dev/null
+++ b/cmd/podman/healthcheck_run.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "fmt"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/adapter"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ healthcheckRunCommand cliconfig.HealthCheckValues
+ healthcheckRunDescription = "run the health check of a container"
+ _healthcheckrunCommand = &cobra.Command{
+ Use: "run [flags] CONTAINER",
+ Short: "run the health check of a container",
+ Long: healthcheckRunDescription,
+ Example: `podman healthcheck run mywebapp`,
+ RunE: func(cmd *cobra.Command, args []string) error {
+ healthcheckRunCommand.InputArgs = args
+ healthcheckRunCommand.GlobalFlags = MainGlobalOpts
+ return healthCheckCmd(&healthcheckRunCommand)
+ },
+ Args: func(cmd *cobra.Command, args []string) error {
+ if len(args) < 1 || len(args) > 1 {
+ return errors.New("must provide the name or ID of one container")
+ }
+ return nil
+ },
+ }
+)
+
+func init() {
+ healthcheckRunCommand.Command = _healthcheckrunCommand
+ healthcheckRunCommand.SetUsageTemplate(UsageTemplate())
+}
+
+func healthCheckCmd(c *cliconfig.HealthCheckValues) error {
+ runtime, err := adapter.GetRuntime(&c.PodmanCommand)
+ if err != nil {
+ return errors.Wrap(err, "could not get runtime")
+ }
+ status, err := runtime.HealthCheck(c)
+ if err != nil {
+ if status == libpod.HealthCheckFailure {
+ fmt.Println("\nunhealthy")
+ }
+ return err
+ }
+ fmt.Println("\nhealthy")
+ return nil
+}
diff --git a/cmd/podman/main.go b/cmd/podman/main.go
index b3faf05c0..97ffa8930 100644
--- a/cmd/podman/main.go
+++ b/cmd/podman/main.go
@@ -52,6 +52,7 @@ var mainCommands = []*cobra.Command{
_stopCommand,
_tagCommand,
_versionCommand,
+ _waitCommand,
imageCommand.Command,
systemCommand.Command,
}
diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go
index c1350bd4d..7067f2429 100644
--- a/cmd/podman/pod.go
+++ b/cmd/podman/pod.go
@@ -29,12 +29,13 @@ var podSubCommands = []*cobra.Command{
_podRestartCommand,
_podRmCommand,
_podStartCommand,
+ _podStatsCommand,
_podStopCommand,
+ _podTopCommand,
_podUnpauseCommand,
}
func init() {
podCommand.AddCommand(podSubCommands...)
- podCommand.AddCommand(getPodSubCommands()...)
podCommand.SetUsageTemplate(UsageTemplate())
}
diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go
index f5edd21f8..2761ce9cc 100644
--- a/cmd/podman/pod_stats.go
+++ b/cmd/podman/pod_stats.go
@@ -13,8 +13,8 @@ import (
tm "github.com/buger/goterm"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/formats"
- "github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/ulule/deepcopier"
@@ -51,10 +51,6 @@ func init() {
}
func podStatsCmd(c *cliconfig.PodStatsValues) error {
- var (
- podFunc func() ([]*libpod.Pod, error)
- )
-
format := c.Format
all := c.All
latest := c.Latest
@@ -76,7 +72,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
all = true
}
- runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
+ runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
}
@@ -87,29 +83,12 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
times = 1
}
- if len(c.InputArgs) > 0 {
- podFunc = func() ([]*libpod.Pod, error) { return getPodsByList(c.InputArgs, runtime) }
- } else if latest {
- podFunc = func() ([]*libpod.Pod, error) {
- latestPod, err := runtime.GetLatestPod()
- if err != nil {
- return nil, err
- }
- return []*libpod.Pod{latestPod}, err
- }
- } else if all {
- podFunc = runtime.GetAllPods
- } else {
- podFunc = runtime.GetRunningPods
- }
-
- pods, err := podFunc()
+ pods, err := runtime.GetStatPods(c)
if err != nil {
return errors.Wrapf(err, "unable to get a list of pods")
}
-
// First we need to get an initial pass of pod/ctr stats (these are not printed)
- var podStats []*libpod.PodContainerStats
+ var podStats []*adapter.PodContainerStats
for _, p := range pods {
cons, err := p.AllContainersByID()
if err != nil {
@@ -120,7 +99,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
for _, c := range cons {
emptyStats[c] = &libpod.ContainerStats{}
}
- ps := libpod.PodContainerStats{
+ ps := adapter.PodContainerStats{
Pod: p,
ContainerStats: emptyStats,
}
@@ -128,10 +107,10 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
}
// Create empty container stat results for our first pass
- var previousPodStats []*libpod.PodContainerStats
+ var previousPodStats []*adapter.PodContainerStats
for _, p := range pods {
cs := make(map[string]*libpod.ContainerStats)
- pcs := libpod.PodContainerStats{
+ pcs := adapter.PodContainerStats{
Pod: p,
ContainerStats: cs,
}
@@ -164,7 +143,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
}
for i := 0; i < times; i += step {
- var newStats []*libpod.PodContainerStats
+ var newStats []*adapter.PodContainerStats
for _, p := range pods {
prevStat := getPreviousPodContainerStats(p.ID(), previousPodStats)
newPodStats, err := p.GetPodStats(prevStat)
@@ -174,7 +153,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
if err != nil {
return err
}
- newPod := libpod.PodContainerStats{
+ newPod := adapter.PodContainerStats{
Pod: p,
ContainerStats: newPodStats,
}
@@ -202,7 +181,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
time.Sleep(time.Second)
previousPodStats := new([]*libpod.PodContainerStats)
deepcopier.Copy(newStats).To(previousPodStats)
- pods, err = podFunc()
+ pods, err = runtime.GetStatPods(c)
if err != nil {
return err
}
@@ -211,7 +190,7 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error {
return nil
}
-func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut {
+func podContainerStatsToPodStatOut(stats []*adapter.PodContainerStats) []*podStatOut {
var out []*podStatOut
for _, p := range stats {
for _, c := range p.ContainerStats {
@@ -295,7 +274,7 @@ func outputToStdOut(stats []*podStatOut) {
w.Flush()
}
-func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats {
+func getPreviousPodContainerStats(podID string, prev []*adapter.PodContainerStats) map[string]*libpod.ContainerStats {
for _, p := range prev {
if podID == p.Pod.ID() {
return p.ContainerStats
@@ -304,7 +283,7 @@ func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats
return map[string]*libpod.ContainerStats{}
}
-func outputJson(stats []*libpod.PodContainerStats) error {
+func outputJson(stats []*adapter.PodContainerStats) error {
b, err := json.MarshalIndent(&stats, "", " ")
if err != nil {
return err
diff --git a/cmd/podman/pod_top.go b/cmd/podman/pod_top.go
index 6a26e3dff..c5383d376 100644
--- a/cmd/podman/pod_top.go
+++ b/cmd/podman/pod_top.go
@@ -2,13 +2,12 @@ package main
import (
"fmt"
+ "github.com/containers/libpod/pkg/adapter"
"os"
"strings"
"text/tabwriter"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/libpodruntime"
- "github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@@ -50,8 +49,9 @@ func init() {
}
func podTopCmd(c *cliconfig.PodTopValues) error {
- var pod *libpod.Pod
- var err error
+ var (
+ descriptors []string
+ )
args := c.InputArgs
if c.ListDescriptors {
@@ -67,39 +67,22 @@ func podTopCmd(c *cliconfig.PodTopValues) error {
return errors.Errorf("you must provide the name or id of a running pod")
}
- runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
+ runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
return errors.Wrapf(err, "error creating libpod runtime")
}
defer runtime.Shutdown(false)
- var descriptors []string
if c.Latest {
descriptors = args
- pod, err = runtime.GetLatestPod()
} else {
descriptors = args[1:]
- pod, err = runtime.LookupPod(args[0])
}
-
- if err != nil {
- return errors.Wrapf(err, "unable to lookup requested container")
- }
-
- podStatus, err := shared.GetPodStatus(pod)
- if err != nil {
- return err
- }
- if podStatus != "Running" {
- return errors.Errorf("pod top can only be used on pods with at least one running container")
- }
-
- psOutput, err := pod.GetPodPidInformation(descriptors)
+ w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
+ psOutput, err := runtime.PodTop(c, descriptors)
if err != nil {
return err
}
-
- w := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
for _, proc := range psOutput {
fmt.Fprintln(w, proc)
}
diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink
index 73e6c15f9..6109bd290 100644
--- a/cmd/podman/varlink/io.podman.varlink
+++ b/cmd/podman/varlink/io.podman.varlink
@@ -549,6 +549,10 @@ method ExportContainer(name: string, path: string) -> (tarfile: string)
# ~~~
method GetContainerStats(name: string) -> (container: ContainerStats)
+# GetContainerStatsWithHistory takes a previous set of container statistics and uses libpod functions
+# to calculate the containers statistics based on current and previous measurements.
+method GetContainerStatsWithHistory(previousStats: ContainerStats) -> (container: ContainerStats)
+
# This method has not be implemented yet.
# method ResizeContainerTty() -> (notimplemented: NotImplemented)
@@ -617,10 +621,10 @@ method UnpauseContainer(name: string) -> (container: string)
# ~~~
method GetAttachSockets(name: string) -> (sockets: Sockets)
-# WaitContainer takes the name or ID of a container and waits until the container stops. Upon stopping, the return
-# code of the container is returned. If the container container cannot be found by ID or name,
-# a [ContainerNotFound](#ContainerNotFound) error is returned.
-method WaitContainer(name: string) -> (exitcode: int)
+# WaitContainer takes the name or ID of a container and waits the given interval in milliseconds until the container
+# stops. Upon stopping, the return code of the container is returned. If the container container cannot be found by ID
+# or name, a [ContainerNotFound](#ContainerNotFound) error is returned.
+method WaitContainer(name: string, interval: int) -> (exitcode: int)
# RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean
# indicating whether to remove builtin volumes. Upon successful removal of the
@@ -952,8 +956,7 @@ method RemovePod(name: string, force: bool) -> (pod: string)
# This method has not be implemented yet.
# method WaitPod() -> (notimplemented: NotImplemented)
-# This method has not been implemented yet.
-# method TopPod() -> (notimplemented: NotImplemented)
+method TopPod(pod: string, latest: bool, descriptors: []string) -> (stats: []string)
# GetPodStats takes the name or ID of a pod and returns a pod name and slice of ContainerStats structure which
# contains attributes like memory and cpu usage. If the pod cannot be found, a [PodNotFound](#PodNotFound)
diff --git a/cmd/podman/wait.go b/cmd/podman/wait.go
index 9df2e3208..6c2a8c9ff 100644
--- a/cmd/podman/wait.go
+++ b/cmd/podman/wait.go
@@ -2,11 +2,11 @@ package main
import (
"fmt"
- "os"
+ "reflect"
"time"
"github.com/containers/libpod/cmd/podman/cliconfig"
- "github.com/containers/libpod/cmd/podman/libpodruntime"
+ "github.com/containers/libpod/pkg/adapter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -49,43 +49,36 @@ func waitCmd(c *cliconfig.WaitValues) error {
return errors.Errorf("you must provide at least one container name or id")
}
- runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand)
+ if c.Interval == 0 {
+ return errors.Errorf("interval must be greater then 0")
+ }
+ interval := time.Duration(c.Interval) * time.Millisecond
+
+ runtime, err := adapter.GetRuntime(&c.PodmanCommand)
if err != nil {
- return errors.Wrapf(err, "error creating libpod runtime")
+ return errors.Wrapf(err, "error creating runtime")
}
defer runtime.Shutdown(false)
+ ok, failures, err := runtime.WaitOnContainers(getContext(), c, interval)
if err != nil {
- return errors.Wrapf(err, "could not get config")
+ return err
}
- var lastError error
- if c.Latest {
- latestCtr, err := runtime.GetLatestContainer()
- if err != nil {
- return errors.Wrapf(err, "unable to wait on latest container")
- }
- args = append(args, latestCtr.ID())
+ for _, id := range ok {
+ fmt.Println(id)
}
- for _, container := range args {
- ctr, err := runtime.LookupContainer(container)
- if err != nil {
- return errors.Wrapf(err, "unable to find container %s", container)
- }
- if c.Interval == 0 {
- return errors.Errorf("interval must be greater then 0")
- }
- returnCode, err := ctr.WaitWithInterval(time.Duration(c.Interval) * time.Millisecond)
- if err != nil {
- if lastError != nil {
- fmt.Fprintln(os.Stderr, lastError)
- }
- lastError = errors.Wrapf(err, "failed to wait for the container %v", container)
- } else {
- fmt.Println(returnCode)
+ if len(failures) > 0 {
+ keys := reflect.ValueOf(failures).MapKeys()
+ lastKey := keys[len(keys)-1].String()
+ lastErr := failures[lastKey]
+ delete(failures, lastKey)
+
+ for _, err := range failures {
+ outputError(err)
}
+ return lastErr
}
-
- return lastError
+ return nil
}
diff --git a/completions/bash/podman b/completions/bash/podman
index 74e3a49d2..a6445e14e 100644
--- a/completions/bash/podman
+++ b/completions/bash/podman
@@ -888,6 +888,26 @@ _podman_container_wait() {
_podman_wait
}
+_podman_healthcheck() {
+ local boolean_options="
+ --help
+ -h
+ "
+ subcommands="
+ run
+ "
+ __podman_subcommands "$subcommands $aliases" && return
+
+ case "$cur" in
+ -*)
+ COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
+ ;;
+ esac
+}
+
_podman_generate() {
local boolean_options="
--help
@@ -2308,6 +2328,15 @@ _podman_load() {
_complete_ "$options_with_args" "$boolean_options"
}
+_podman_cp() {
+ local boolean_options="
+ --help
+ -h
+ --extract
+ "
+ _complete_ "$boolean_options"
+}
+
_podman_login() {
local options_with_args="
--username
@@ -2338,6 +2367,27 @@ _podman_logout() {
_complete_ "$options_with_args" "$boolean_options"
}
+_podman_healtcheck_run() {
+ local options_with_args=""
+
+ local boolean_options="
+ -h
+ --help
+ "
+
+ case "$cur" in
+ -*)
+ COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=( $( compgen -W "
+ $(__podman_containers --all)
+ " -- "$cur" ) )
+ __ltrim_colon_completions "$cur"
+ ;;
+ esac
+}
+
_podman_generate_kube() {
local options_with_args=""
@@ -2974,11 +3024,13 @@ _podman_podman() {
build
commit
container
+ cp
create
diff
exec
export
generate
+ healthcheck
history
image
images
diff --git a/docs/podman-cp.1.md b/docs/podman-cp.1.md
index 37426b236..7774542e8 100644
--- a/docs/podman-cp.1.md
+++ b/docs/podman-cp.1.md
@@ -54,6 +54,11 @@ You can also use : when specifying paths to a **SRC_PATH** or **DEST_PATH** on a
If you use a : in a local machine path, you must be explicit with a relative or absolute path, for example:
`/path/to/file:name.txt` or `./file:name.txt`
+## OPTIONS
+
+**--extract**
+
+Extract the tar file into the destination directory. If the destination directory is not provided, extract the tar file into the root directory.
## ALTERNATIVES
@@ -100,5 +105,7 @@ podman cp containerID:/myapp/ /myapp/
podman cp containerID:/home/myuser/. /home/myuser/
+podman cp --extract /home/myuser/myfiles.tar.gz containerID:/myfiles
+
## SEE ALSO
podman(1), podman-mount(1), podman-umount(1)
diff --git a/docs/podman-healthcheck-run.1.md b/docs/podman-healthcheck-run.1.md
new file mode 100644
index 000000000..8cecb8eaa
--- /dev/null
+++ b/docs/podman-healthcheck-run.1.md
@@ -0,0 +1,39 @@
+% podman-healthcheck-run(1)
+
+## NAME
+podman\-healthcheck\-run- Run a container healthcheck
+
+## SYNOPSIS
+**podman healthcheck run** [*options*] *container*
+
+## DESCRIPTION
+
+Runs the healthcheck command defined in a running container manually. The resulting error codes are defined
+as follows:
+
+* 0 = healthcheck command succeeded
+* 1 = healthcheck command failed
+* 125 = an error has occurred
+
+Possible errors that can occur during the healthcheck are:
+* unable to find the container
+* container has no defined healthcheck
+* container is not running
+
+## OPTIONS
+**--help**
+
+Print usage statement
+
+
+## EXAMPLES
+
+```
+$ podman healtcheck run mywebapp
+```
+
+## SEE ALSO
+podman-healthcheck(1)
+
+## HISTORY
+Feb 2019, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/docs/podman-healthcheck.1.md b/docs/podman-healthcheck.1.md
new file mode 100644
index 000000000..68d403231
--- /dev/null
+++ b/docs/podman-healthcheck.1.md
@@ -0,0 +1,22 @@
+% podman-healthcheck(1)
+
+## NAME
+podman\-healthcheck- Manage healthchecks for containers
+
+## SYNOPSIS
+**podman healthcheck** *subcommand*
+
+## DESCRIPTION
+podman healthcheck is a set of subcommands that manage container healthchecks
+
+## SUBCOMMANDS
+
+| Command | Man Page | Description |
+| ------- | ------------------------------------------------- | ------------------------------------------------------------------------------ |
+| run | [podman-healthcheck-run(1)](podman-healthcheck-run.1.md) | Run a container healthcheck |
+
+## SEE ALSO
+podman(1)
+
+## HISTORY
+Feb 2019, Originally compiled by Brent Baude <bbaude@redhat.com>
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go
index c226a0617..92a7b1538 100644
--- a/libpod/boltdb_state.go
+++ b/libpod/boltdb_state.go
@@ -382,6 +382,11 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
return err
}
+ namesBucket, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
@@ -395,41 +400,59 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
// It might not be in our namespace, but
// getContainerFromDB() will handle that case.
id = []byte(idOrName)
- } else {
- // They did not give us a full container ID.
- // Search for partial ID or full name matches
- // Use else-if in case the name is set to a partial ID
- exists := false
- err = idBucket.ForEach(func(checkID, checkName []byte) error {
- // If the container isn't in our namespace, we
- // can't match it
- if s.namespaceBytes != nil {
- ns := nsBucket.Get(checkID)
- if !bytes.Equal(ns, s.namespaceBytes) {
- return nil
- }
+ return s.getContainerFromDB(id, ctr, ctrBucket)
+ }
+
+ // Next, check if the full name was given
+ isPod := false
+ fullID := namesBucket.Get([]byte(idOrName))
+ if fullID != nil {
+ // The name exists and maps to an ID.
+ // However, we are not yet certain the ID is a
+ // container.
+ ctrExists = ctrBucket.Bucket(fullID)
+ if ctrExists != nil {
+ // A container bucket matching the full ID was
+ // found.
+ return s.getContainerFromDB(fullID, ctr, ctrBucket)
+ }
+ // Don't error if we have a name match but it's not a
+ // container - there's a chance we have a container with
+ // an ID starting with those characters.
+ // However, so we can return a good error, note whether
+ // this is a pod.
+ isPod = true
+ }
+
+ // We were not given a full container ID or name.
+ // Search for partial ID matches.
+ exists := false
+ err = idBucket.ForEach(func(checkID, checkName []byte) error {
+ // If the container isn't in our namespace, we
+ // can't match it
+ if s.namespaceBytes != nil {
+ ns := nsBucket.Get(checkID)
+ if !bytes.Equal(ns, s.namespaceBytes) {
+ return nil
}
- if string(checkName) == idOrName {
- if exists {
- return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
- } else if strings.HasPrefix(string(checkID), idOrName) {
- if exists {
- return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
+ }
+ if strings.HasPrefix(string(checkID), idOrName) {
+ if exists {
+ return errors.Wrapf(ErrCtrExists, "more than one result for container ID %s", idOrName)
}
+ id = checkID
+ exists = true
+ }
- return nil
- })
- if err != nil {
- return err
- } else if !exists {
- return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
+ return nil
+ })
+ if err != nil {
+ return err
+ } else if !exists {
+ if isPod {
+ return errors.Wrapf(ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
}
+ return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
}
return s.getContainerFromDB(id, ctr, ctrBucket)
@@ -941,6 +964,11 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
return err
}
+ namesBkt, err := getNamesBucket(tx)
+ if err != nil {
+ return err
+ }
+
nsBkt, err := getNSBucket(tx)
if err != nil {
return err
@@ -954,41 +982,56 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
// It might not be in our namespace, but getPodFromDB()
// will handle that case.
id = []byte(idOrName)
- } else {
- // They did not give us a full pod ID.
- // Search for partial ID or full name matches
- // Use else-if in case the name is set to a partial ID
- exists := false
- err = idBucket.ForEach(func(checkID, checkName []byte) error {
- // If the pod isn't in our namespace, we
- // can't match it
- if s.namespaceBytes != nil {
- ns := nsBkt.Get(checkID)
- if !bytes.Equal(ns, s.namespaceBytes) {
- return nil
- }
+ return s.getPodFromDB(id, pod, podBkt)
+ }
+
+ // Next, check if the full name was given
+ isCtr := false
+ fullID := namesBkt.Get([]byte(idOrName))
+ if fullID != nil {
+ // The name exists and maps to an ID.
+ // However, we aren't yet sure if the ID is a pod.
+ podExists = podBkt.Bucket(fullID)
+ if podExists != nil {
+ // A pod bucket matching the full ID was found.
+ return s.getPodFromDB(fullID, pod, podBkt)
+ }
+ // Don't error if we have a name match but it's not a
+ // pod - there's a chance we have a pod with an ID
+ // starting with those characters.
+ // However, so we can return a good error, note whether
+ // this is a container.
+ isCtr = true
+ }
+ // They did not give us a full pod name or ID.
+ // Search for partial ID matches.
+ exists := false
+ err = idBucket.ForEach(func(checkID, checkName []byte) error {
+ // If the pod isn't in our namespace, we
+ // can't match it
+ if s.namespaceBytes != nil {
+ ns := nsBkt.Get(checkID)
+ if !bytes.Equal(ns, s.namespaceBytes) {
+ return nil
}
- if string(checkName) == idOrName {
- if exists {
- return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
- } else if strings.HasPrefix(string(checkID), idOrName) {
- if exists {
- return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
- }
- id = checkID
- exists = true
+ }
+ if strings.HasPrefix(string(checkID), idOrName) {
+ if exists {
+ return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName)
}
+ id = checkID
+ exists = true
+ }
- return nil
- })
- if err != nil {
- return err
- } else if !exists {
- return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName)
+ return nil
+ })
+ if err != nil {
+ return err
+ } else if !exists {
+ if isCtr {
+ return errors.Wrapf(ErrNoSuchPod, "%s is a container, not a pod", idOrName)
}
+ return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName)
}
// We might have found a container ID, but it's OK
diff --git a/libpod/container.go b/libpod/container.go
index 75f4a4a4f..2381f53ad 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -10,6 +10,7 @@ import (
"github.com/containernetworking/cni/pkg/types"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/libpod/lock"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
@@ -365,6 +366,9 @@ type ContainerConfig struct {
// Systemd tells libpod to setup the container in systemd mode
Systemd bool `json:"systemd"`
+
+ // HealtchCheckConfig has the health check command and related timings
+ HealthCheckConfig *manifest.Schema2HealthConfig
}
// ContainerStatus returns a string representation for users
@@ -1085,3 +1089,14 @@ func (c *Container) ContainerState() (*ContainerState, error) {
deepcopier.Copy(c.state).To(returnConfig)
return c.state, nil
}
+
+// HasHealthCheck returns bool as to whether there is a health check
+// defined for the container
+func (c *Container) HasHealthCheck() bool {
+ return c.config.HealthCheckConfig != nil
+}
+
+// HealthCheckConfig returns the command and timing attributes of the health check
+func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig {
+ return c.config.HealthCheckConfig
+}
diff --git a/libpod/container_internal.go b/libpod/container_internal.go
index 08945c410..d86062e38 100644
--- a/libpod/container_internal.go
+++ b/libpod/container_internal.go
@@ -34,8 +34,8 @@ const (
)
var (
- // localeToLanguage maps from locale values to language tags.
- localeToLanguage = map[string]string{
+ // localeToLanguageMap maps from locale values to language tags.
+ localeToLanguageMap = map[string]string{
"": "und-u-va-posix",
"c": "und-u-va-posix",
"posix": "und-u-va-posix",
@@ -1281,6 +1281,16 @@ func (c *Container) saveSpec(spec *spec.Spec) error {
return nil
}
+// localeToLanguage translates POSIX locale strings to BCP 47 language tags.
+func localeToLanguage(locale string) string {
+ locale = strings.Replace(strings.SplitN(locale, ".", 2)[0], "_", "-", 1)
+ langString, ok := localeToLanguageMap[strings.ToLower(locale)]
+ if !ok {
+ langString = locale
+ }
+ return langString
+}
+
// Warning: precreate hooks may alter 'config' in place.
func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (extensionStageHooks map[string][]spec.Hook, err error) {
var locale string
@@ -1296,11 +1306,7 @@ func (c *Container) setupOCIHooks(ctx context.Context, config *spec.Spec) (exten
}
}
- langString, ok := localeToLanguage[strings.ToLower(locale)]
- if !ok {
- langString = locale
- }
-
+ langString := localeToLanguage(locale)
lang, err := language.Parse(langString)
if err != nil {
logrus.Warnf("failed to parse language %q: %s", langString, err)
diff --git a/libpod/container_internal_test.go b/libpod/container_internal_test.go
index f1e2b70a7..1654af929 100644
--- a/libpod/container_internal_test.go
+++ b/libpod/container_internal_test.go
@@ -17,6 +17,54 @@ import (
// hookPath is the path to an example hook executable.
var hookPath string
+func TestLocaleToLanguage(t *testing.T) {
+ for _, testCase := range []struct {
+ locale string
+ language string
+ }{
+ {
+ locale: "",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "C",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "POSIX",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "c",
+ language: "und-u-va-posix",
+ },
+ {
+ locale: "en",
+ language: "en",
+ },
+ {
+ locale: "en_US",
+ language: "en-US",
+ },
+ {
+ locale: "en.UTF-8",
+ language: "en",
+ },
+ {
+ locale: "en_US.UTF-8",
+ language: "en-US",
+ },
+ {
+ locale: "does-not-exist",
+ language: "does-not-exist",
+ },
+ } {
+ t.Run(testCase.locale, func(t *testing.T) {
+ assert.Equal(t, testCase.language, localeToLanguage(testCase.locale))
+ })
+ }
+}
+
func TestPostDeleteHooks(t *testing.T) {
ctx := context.Background()
dir, err := ioutil.TempDir("", "libpod_test_")
diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go
new file mode 100644
index 000000000..81addb9a8
--- /dev/null
+++ b/libpod/healthcheck.go
@@ -0,0 +1,92 @@
+package libpod
+
+import (
+ "os"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// HealthCheckStatus represents the current state of a container
+type HealthCheckStatus int
+
+const (
+ // HealthCheckSuccess means the health worked
+ HealthCheckSuccess HealthCheckStatus = iota
+ // HealthCheckFailure means the health ran and failed
+ HealthCheckFailure HealthCheckStatus = iota
+ // HealthCheckContainerStopped means the health check cannot
+ // be run because the container is stopped
+ HealthCheckContainerStopped HealthCheckStatus = iota
+ // HealthCheckContainerNotFound means the container could
+ // not be found in local store
+ HealthCheckContainerNotFound HealthCheckStatus = iota
+ // HealthCheckNotDefined means the container has no health
+ // check defined in it
+ HealthCheckNotDefined HealthCheckStatus = iota
+ // HealthCheckInternalError means somes something failed obtaining or running
+ // a given health check
+ HealthCheckInternalError HealthCheckStatus = iota
+ // HealthCheckDefined means the healthcheck was found on the container
+ HealthCheckDefined HealthCheckStatus = iota
+)
+
+// HealthCheck verifies the state and validity of the healthcheck configuration
+// on the container and then executes the healthcheck
+func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) {
+ container, err := r.LookupContainer(name)
+ if err != nil {
+ return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name)
+ }
+ hcStatus, err := checkHealthCheckCanBeRun(container)
+ if err == nil {
+ return container.RunHealthCheck()
+ }
+ return hcStatus, err
+}
+
+// RunHealthCheck runs the health check as defined by the container
+func (c *Container) RunHealthCheck() (HealthCheckStatus, error) {
+ var newCommand []string
+ hcStatus, err := checkHealthCheckCanBeRun(c)
+ if err != nil {
+ return hcStatus, err
+ }
+ hcCommand := c.HealthCheckConfig().Test
+ if len(hcCommand) > 0 && hcCommand[0] == "CMD-SHELL" {
+ newCommand = []string{"sh", "-c"}
+ newCommand = append(newCommand, hcCommand[1:]...)
+ } else {
+ newCommand = hcCommand
+ }
+ // TODO when history/logging is implemented for healthcheck, we need to change the output streams
+ // so we can capture i/o
+ streams := new(AttachStreams)
+ streams.OutputStream = os.Stdout
+ streams.ErrorStream = os.Stderr
+ streams.InputStream = os.Stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID())
+ if err := c.Exec(false, false, []string{}, newCommand, "", "", streams, 0); err != nil {
+ return HealthCheckFailure, err
+ }
+ return HealthCheckSuccess, nil
+}
+
+func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) {
+ cstate, err := c.State()
+ if err != nil {
+ return HealthCheckInternalError, err
+ }
+ if cstate != ContainerStateRunning {
+ return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID())
+ }
+ if !c.HasHealthCheck() {
+ return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID())
+ }
+ return HealthCheckDefined, nil
+}
diff --git a/libpod/image/image.go b/libpod/image/image.go
index b20419d7b..8c98de3d3 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -1151,3 +1151,32 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
return nil
}
+
+// GetConfigBlob returns a schema2image. If the image is not a schema2, then
+// it will return an error
+func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) {
+ imageRef, err := i.toImageRef(ctx)
+ if err != nil {
+ return nil, err
+ }
+ b, err := imageRef.ConfigBlob(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID())
+ }
+ blob := manifest.Schema2Image{}
+ if err := json.Unmarshal(b, &blob); err != nil {
+ return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID())
+ }
+ return &blob, nil
+
+}
+
+// GetHealthCheck returns a HealthConfig for an image. This function only works with
+// schema2 images.
+func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) {
+ configBlob, err := i.GetConfigBlob(ctx)
+ if err != nil {
+ return nil, err
+ }
+ return configBlob.ContainerConfig.Healthcheck, nil
+}
diff --git a/libpod/oci.go b/libpod/oci.go
index 2cbf25699..c3b5f9af2 100644
--- a/libpod/oci.go
+++ b/libpod/oci.go
@@ -805,6 +805,12 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty
execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir))
+ if preserveFDs > 0 {
+ for fd := 3; fd < 3+preserveFDs; fd++ {
+ execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)))
+ }
+ }
+
if err := execCmd.Start(); err != nil {
return nil, errors.Wrapf(err, "cannot start container %s", c.ID())
}
diff --git a/libpod/options.go b/libpod/options.go
index 1e8592a25..5ad2824d9 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -7,6 +7,7 @@ import (
"regexp"
"syscall"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
@@ -1469,3 +1470,14 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
return nil
}
}
+
+// WithHealthCheck adds the healthcheck to the container config
+func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return ErrCtrFinalized
+ }
+ ctr.config.HealthCheckConfig = healthCheck
+ return nil
+ }
+}
diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go
index c378d18e4..9063390bd 100644
--- a/libpod/runtime_pod_linux.go
+++ b/libpod/runtime_pod_linux.go
@@ -95,9 +95,12 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod,
if pod.config.UsePodCgroup {
logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath)
}
- if pod.HasInfraContainer() != pod.SharesNamespaces() {
+ if !pod.HasInfraContainer() && pod.SharesNamespaces() {
return nil, errors.Errorf("Pods must have an infra container to share namespaces")
}
+ if pod.HasInfraContainer() && !pod.SharesNamespaces() {
+ logrus.Warnf("Pod has an infra container, but shares no namespaces")
+ }
if err := r.state.AddPod(pod); err != nil {
return nil, errors.Wrapf(err, "error adding pod to state")
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index fcce9bb86..756369196 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -4,7 +4,9 @@ package adapter
import (
"context"
+ "strconv"
"syscall"
+ "time"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/libpod"
@@ -103,3 +105,25 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
}
return ok, failures, nil
}
+
+// WaitOnContainers waits for all given container(s) to stop
+func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) {
+ var (
+ ok = []string{}
+ failures = map[string]error{}
+ )
+
+ ctrs, err := shortcuts.GetContainersByContext(false, cli.Latest, cli.InputArgs, r.Runtime)
+ if err != nil {
+ return ok, failures, err
+ }
+
+ for _, c := range ctrs {
+ if returnCode, err := c.WaitWithInterval(interval); err == nil {
+ ok = append(ok, strconv.Itoa(int(returnCode)))
+ } else {
+ failures[c.ID()] = err
+ }
+ }
+ return ok, failures, err
+}
diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go
index 45926ccf9..5646d2297 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -6,7 +6,9 @@ import (
"context"
"encoding/json"
"errors"
+ "strconv"
"syscall"
+ "time"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
@@ -173,6 +175,30 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
return ok, failures, nil
}
+// WaitOnContainers waits for all given container(s) to stop.
+// interval is currently ignored.
+func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) {
+ var (
+ ok = []string{}
+ failures = map[string]error{}
+ )
+
+ ids, err := iopodman.GetContainersByContext().Call(r.Conn, false, cli.Latest, cli.InputArgs)
+ if err != nil {
+ return ok, failures, err
+ }
+
+ for _, id := range ids {
+ stopped, err := iopodman.WaitContainer().Call(r.Conn, id, int64(interval))
+ if err != nil {
+ failures[id] = err
+ } else {
+ ok = append(ok, strconv.FormatInt(stopped, 10))
+ }
+ }
+ return ok, failures, nil
+}
+
// BatchContainerOp is wrapper func to mimic shared's function with a similar name meant for libpod
func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContainerStruct, error) {
// TODO If pod ps ever shows container's sizes, re-enable this code; otherwise it isn't needed
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index 706a8fe96..669971789 100644
--- a/pkg/adapter/pods.go
+++ b/pkg/adapter/pods.go
@@ -4,6 +4,7 @@ package adapter
import (
"context"
+ "github.com/pkg/errors"
"strings"
"github.com/containers/libpod/cmd/podman/cliconfig"
@@ -17,6 +18,13 @@ type Pod struct {
*libpod.Pod
}
+// PodContainerStats is struct containing an adapter Pod and a libpod
+// ContainerStats and is used primarily for outputing pod stats.
+type PodContainerStats struct {
+ Pod *Pod
+ ContainerStats map[string]*libpod.ContainerStats
+}
+
// RemovePods ...
func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValues) ([]string, []error) {
var (
@@ -321,3 +329,55 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV
return restartIDs, containerErrors, restartErrors
}
+
+// PodTop is a wrapper function to call GetPodPidInformation in libpod and return its results
+// for output
+func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) {
+ var (
+ pod *Pod
+ err error
+ )
+
+ if c.Latest {
+ pod, err = r.GetLatestPod()
+ } else {
+ pod, err = r.LookupPod(c.InputArgs[0])
+ }
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to lookup requested container")
+ }
+ podStatus, err := pod.GetPodStatus()
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to get status for pod %s", pod.ID())
+ }
+ if podStatus != "Running" {
+ return nil, errors.Errorf("pod top can only be used on pods with at least one running container")
+ }
+ return pod.GetPodPidInformation(descriptors)
+}
+
+// GetStatPods returns pods for use in pod stats
+func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) {
+ var (
+ adapterPods []*Pod
+ pods []*libpod.Pod
+ err error
+ )
+
+ if len(c.InputArgs) > 0 || c.Latest || c.All {
+ pods, err = shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime)
+ } else {
+ pods, err = r.Runtime.GetRunningPods()
+ }
+ if err != nil {
+ return nil, err
+ }
+ // convert libpod pods to adapter pods
+ for _, p := range pods {
+ adapterPod := Pod{
+ p,
+ }
+ adapterPods = append(adapterPods, &adapterPod)
+ }
+ return adapterPods, nil
+}
diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go
index 220f7163f..ef8de90a6 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/varlinkapi"
"github.com/pkg/errors"
"github.com/ulule/deepcopier"
)
@@ -21,6 +22,13 @@ type Pod struct {
remotepod
}
+// PodContainerStats is struct containing an adapter Pod and a libpod
+// ContainerStats and is used primarily for outputing pod stats.
+type PodContainerStats struct {
+ Pod *Pod
+ ContainerStats map[string]*libpod.ContainerStats
+}
+
type remotepod struct {
config *libpod.PodConfig
state *libpod.PodInspectState
@@ -399,3 +407,103 @@ func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartV
}
return restartIDs, nil, restartErrors
}
+
+// PodTop gets top statistics for a pod
+func (r *LocalRuntime) PodTop(c *cliconfig.PodTopValues, descriptors []string) ([]string, error) {
+ var (
+ latest bool
+ podName string
+ )
+ if c.Latest {
+ latest = true
+ } else {
+ podName = c.InputArgs[0]
+ }
+ return iopodman.TopPod().Call(r.Conn, podName, latest, descriptors)
+}
+
+// GetStatPods returns pods for use in pod stats
+func (r *LocalRuntime) GetStatPods(c *cliconfig.PodStatsValues) ([]*Pod, error) {
+ var (
+ pods []*Pod
+ err error
+ podIDs []string
+ running bool
+ )
+
+ if len(c.InputArgs) > 0 || c.Latest || c.All {
+ podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
+ } else {
+ podIDs, err = iopodman.GetPodsByContext().Call(r.Conn, true, false, []string{})
+ running = true
+ }
+ if err != nil {
+ return nil, err
+ }
+ for _, p := range podIDs {
+ pod, err := r.Inspect(p)
+ if err != nil {
+ return nil, err
+ }
+ if running {
+ status, err := pod.GetPodStatus()
+ if err != nil {
+ // if we cannot get the status of the pod, skip and move on
+ continue
+ }
+ if strings.ToUpper(status) != "RUNNING" {
+ // if the pod is not running, skip and move on as well
+ continue
+ }
+ }
+ pods = append(pods, pod)
+ }
+ return pods, nil
+}
+
+// GetPodStats returns the stats for each of its containers
+func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerStats) (map[string]*libpod.ContainerStats, error) {
+ var (
+ ok bool
+ prevStat *libpod.ContainerStats
+ )
+ newContainerStats := make(map[string]*libpod.ContainerStats)
+ containers, err := p.AllContainers()
+ if err != nil {
+ return nil, err
+ }
+ for _, c := range containers {
+ if prevStat, ok = previousContainerStats[c.ID()]; !ok {
+ prevStat = &libpod.ContainerStats{ContainerID: c.ID()}
+ }
+ cStats := iopodman.ContainerStats{
+ Id: prevStat.ContainerID,
+ Name: prevStat.Name,
+ Cpu: prevStat.CPU,
+ Cpu_nano: int64(prevStat.CPUNano),
+ System_nano: int64(prevStat.SystemNano),
+ Mem_usage: int64(prevStat.MemUsage),
+ Mem_limit: int64(prevStat.MemLimit),
+ Mem_perc: prevStat.MemPerc,
+ Net_input: int64(prevStat.NetInput),
+ Net_output: int64(prevStat.NetOutput),
+ Block_input: int64(prevStat.BlockInput),
+ Block_output: int64(prevStat.BlockOutput),
+ Pids: int64(prevStat.PIDs),
+ }
+ stats, err := iopodman.GetContainerStatsWithHistory().Call(p.Runtime.Conn, cStats)
+ if err != nil {
+ return nil, err
+ }
+ newStats := varlinkapi.ContainerStatsToLibpodContainerStats(stats)
+ // If the container wasn't running, don't include it
+ // but also suppress the error
+ if err != nil && errors.Cause(err) != libpod.ErrCtrStateInvalid {
+ return nil, err
+ }
+ if err == nil {
+ newContainerStats[c.ID()] = &newStats
+ }
+ }
+ return newContainerStats, nil
+}
diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go
index 5be2ca150..732b89530 100644
--- a/pkg/adapter/runtime.go
+++ b/pkg/adapter/runtime.go
@@ -332,3 +332,8 @@ func IsImageNotFound(err error) bool {
}
return false
}
+
+// HealthCheck is a wrapper to same named function in libpod
+func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
+ return r.Runtime.HealthCheck(c.InputArgs[0])
+}
diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go
index 14cda7bff..10c25c3f3 100644
--- a/pkg/adapter/runtime_remote.go
+++ b/pkg/adapter/runtime_remote.go
@@ -746,3 +746,8 @@ func IsImageNotFound(err error) bool {
}
return false
}
+
+// HealthCheck executes a container's healthcheck over a varlink connection
+func (r *LocalRuntime) HealthCheck(c *cliconfig.HealthCheckValues) (libpod.HealthCheckStatus, error) {
+ return -1, libpod.ErrNotImplemented
+}
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go
index eccd41ff9..a1873086e 100644
--- a/pkg/spec/config_linux.go
+++ b/pkg/spec/config_linux.go
@@ -46,19 +46,32 @@ func devicesFromPath(g *generate.Generator, devicePath string) error {
return errors.Wrapf(err, "cannot stat %s", devicePath)
}
if st.IsDir() {
+ found := false
+ src := resolvedDevicePath
+ dest := src
+ var devmode string
+ if len(devs) > 1 {
+ if len(devs[1]) > 0 && devs[1][0] == '/' {
+ dest = devs[1]
+ } else {
+ devmode = devs[1]
+ }
+ }
if len(devs) > 2 {
- return errors.Wrapf(unix.EINVAL, "not allowed to specify destination with a directory %s", devicePath)
+ if devmode != "" {
+ return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath)
+ }
+ devmode = devs[2]
}
- found := false
+
// mount the internal devices recursively
if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error {
if f.Mode()&os.ModeDevice == os.ModeDevice {
found = true
- device := dpath
-
- if len(devs) > 1 {
- device = fmt.Sprintf("%s:%s", dpath, devs[1])
+ device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src)))
+ if devmode != "" {
+ device = fmt.Sprintf("%s:%s", device, devmode)
}
if err := addDevice(g, device); err != nil {
return errors.Wrapf(err, "failed to add %s device", dpath)
diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go
index 31039bfdf..6e7b297d2 100644
--- a/pkg/spec/createconfig.go
+++ b/pkg/spec/createconfig.go
@@ -9,6 +9,7 @@ import (
"strings"
"syscall"
+ "github.com/containers/image/manifest"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/namespaces"
"github.com/containers/libpod/pkg/rootless"
@@ -86,6 +87,8 @@ type CreateConfig struct {
Env map[string]string //env
ExposedPorts map[nat.Port]struct{}
GroupAdd []string // group-add
+ HasHealthCheck bool
+ HealthCheck *manifest.Schema2HealthConfig
HostAdd []string //add-host
Hostname string //hostname
Image string
@@ -559,6 +562,10 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l
// Always use a cleanup process to clean up Podman after termination
options = append(options, libpod.WithExitCommand(c.createExitCommand()))
+ if c.HasHealthCheck {
+ options = append(options, libpod.WithHealthCheck(c.HealthCheck))
+ logrus.Debugf("New container has a health check")
+ }
return options, nil
}
diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go
index 27b8d15d2..fe38a7cdc 100644
--- a/pkg/varlinkapi/containers.go
+++ b/pkg/varlinkapi/containers.go
@@ -360,17 +360,16 @@ func (i *LibpodAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) err
}
// WaitContainer ...
-func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string) error {
+func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string, interval int64) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
- exitCode, err := ctr.Wait()
+ exitCode, err := ctr.WaitWithInterval(time.Duration(interval))
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyWaitContainer(int64(exitCode))
-
}
// RemoveContainer ...
@@ -552,3 +551,54 @@ func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) e
}
return call.ReplyContainerStateData(string(b))
}
+
+// GetContainerStatsWithHistory is a varlink endpoint that returns container stats based on current and
+// previous statistics
+func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error {
+ con, err := i.Runtime.LookupContainer(prevStats.Id)
+ if err != nil {
+ return call.ReplyContainerNotFound(prevStats.Id, err.Error())
+ }
+ previousStats := ContainerStatsToLibpodContainerStats(prevStats)
+ stats, err := con.GetContainerStats(&previousStats)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ cStats := iopodman.ContainerStats{
+ Id: stats.ContainerID,
+ Name: stats.Name,
+ Cpu: stats.CPU,
+ Cpu_nano: int64(stats.CPUNano),
+ System_nano: int64(stats.SystemNano),
+ Mem_usage: int64(stats.MemUsage),
+ Mem_limit: int64(stats.MemLimit),
+ Mem_perc: stats.MemPerc,
+ Net_input: int64(stats.NetInput),
+ Net_output: int64(stats.NetOutput),
+ Block_input: int64(stats.BlockInput),
+ Block_output: int64(stats.BlockOutput),
+ Pids: int64(stats.PIDs),
+ }
+ return call.ReplyGetContainerStatsWithHistory(cStats)
+}
+
+// ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod
+// container stats
+func ContainerStatsToLibpodContainerStats(stats iopodman.ContainerStats) libpod.ContainerStats {
+ cstats := libpod.ContainerStats{
+ ContainerID: stats.Id,
+ Name: stats.Name,
+ CPU: stats.Cpu,
+ CPUNano: uint64(stats.Cpu_nano),
+ SystemNano: uint64(stats.System_nano),
+ MemUsage: uint64(stats.Mem_usage),
+ MemLimit: uint64(stats.Mem_limit),
+ MemPerc: stats.Mem_perc,
+ NetInput: uint64(stats.Net_input),
+ NetOutput: uint64(stats.Net_output),
+ BlockInput: uint64(stats.Block_input),
+ BlockOutput: uint64(stats.Block_output),
+ PIDs: uint64(stats.Pids),
+ }
+ return cstats
+}
diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go
index 4ca4c4270..c79cee4c2 100644
--- a/pkg/varlinkapi/pods.go
+++ b/pkg/varlinkapi/pods.go
@@ -2,6 +2,7 @@ package varlinkapi
import (
"encoding/json"
+ "fmt"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/rootless"
"syscall"
@@ -299,3 +300,33 @@ func (i *LibpodAPI) PodStateData(call iopodman.VarlinkCall, name string) error {
}
return call.ReplyPodStateData(string(b))
}
+
+// TopPod provides the top stats for a given or latest pod
+func (i *LibpodAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, descriptors []string) error {
+ var (
+ pod *libpod.Pod
+ err error
+ )
+ if latest {
+ name = "latest"
+ pod, err = i.Runtime.GetLatestPod()
+ } else {
+ pod, err = i.Runtime.LookupPod(name)
+ }
+ if err != nil {
+ return call.ReplyPodNotFound(name, err.Error())
+ }
+
+ podStatus, err := shared.GetPodStatus(pod)
+ if err != nil {
+ return call.ReplyErrorOccurred(fmt.Sprintf("unable to get status for pod %s", pod.ID()))
+ }
+ if podStatus != "Running" {
+ return call.ReplyErrorOccurred("pod top can only be used on pods with at least one running container")
+ }
+ reply, err := pod.GetPodPidInformation(descriptors)
+ if err != nil {
+ return call.ReplyErrorOccurred(err.Error())
+ }
+ return call.ReplyTopPod(reply)
+}
diff --git a/test/e2e/config.go b/test/e2e/config.go
index 8116d993b..3fdb9e116 100644
--- a/test/e2e/config.go
+++ b/test/e2e/config.go
@@ -6,4 +6,5 @@ var (
ALPINE = "docker.io/library/alpine:latest"
infra = "k8s.gcr.io/pause:3.1"
BB = "docker.io/library/busybox:latest"
+ healthcheck = "docker.io/libpod/alpine_healthcheck:latest"
)
diff --git a/test/e2e/config_amd64.go b/test/e2e/config_amd64.go
index 3459bea6d..d02de7a6e 100644
--- a/test/e2e/config_amd64.go
+++ b/test/e2e/config_amd64.go
@@ -3,7 +3,7 @@ package integration
var (
STORAGE_OPTIONS = "--storage-driver vfs"
ROOTLESS_STORAGE_OPTIONS = "--storage-driver vfs"
- CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels}
+ CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels, healthcheck}
nginx = "quay.io/libpod/alpine_nginx:latest"
BB_GLIBC = "docker.io/library/busybox:glibc"
registry = "docker.io/library/registry:2"
diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go
new file mode 100644
index 000000000..921d325c3
--- /dev/null
+++ b/test/e2e/healthcheck_run_test.go
@@ -0,0 +1,85 @@
+// +build !remoteclient
+
+package integration
+
+import (
+ "fmt"
+ "os"
+
+ . "github.com/containers/libpod/test/utils"
+ . "github.com/onsi/ginkgo"
+ . "github.com/onsi/gomega"
+)
+
+var _ = Describe("Podman healthcheck run", func() {
+ var (
+ tempdir string
+ err error
+ podmanTest *PodmanTestIntegration
+ )
+
+ BeforeEach(func() {
+ tempdir, err = CreateTempDirInTempDir()
+ if err != nil {
+ os.Exit(1)
+ }
+ podmanTest = PodmanTestCreate(tempdir)
+ podmanTest.RestoreAllArtifacts()
+ })
+
+ AfterEach(func() {
+ podmanTest.Cleanup()
+ f := CurrentGinkgoTestDescription()
+ timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds())
+ GinkgoWriter.Write([]byte(timedResult))
+
+ })
+
+ It("podman healthcheck run bogus container", func() {
+ session := podmanTest.Podman([]string{"healthcheck", "run", "foobar"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman healthcheck on valid container", func() {
+ podmanTest.RestoreArtifact(healthcheck)
+ session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"})
+ hc.WaitWithDefaultTimeout()
+ Expect(hc.ExitCode()).To(Equal(0))
+ })
+
+ It("podman healthcheck that should fail", func() {
+ session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "docker.io/libpod/badhealthcheck:latest"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"})
+ hc.WaitWithDefaultTimeout()
+ Expect(hc.ExitCode()).To(Equal(1))
+ })
+
+ It("podman healthcheck on stopped container", func() {
+ podmanTest.RestoreArtifact(healthcheck)
+ session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", healthcheck, "ls"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"})
+ hc.WaitWithDefaultTimeout()
+ Expect(hc.ExitCode()).To(Equal(125))
+ })
+
+ It("podman healthcheck on container without healthcheck", func() {
+ session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", ALPINE, "top"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ hc := podmanTest.Podman([]string{"healthcheck", "run", "hc"})
+ hc.WaitWithDefaultTimeout()
+ Expect(hc.ExitCode()).To(Equal(125))
+ })
+})
diff --git a/test/e2e/run_device_test.go b/test/e2e/run_device_test.go
index 4f26ac8ee..8734bb71c 100644
--- a/test/e2e/run_device_test.go
+++ b/test/e2e/run_device_test.go
@@ -72,4 +72,12 @@ var _ = Describe("Podman run device", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Not(Equal(0)))
})
+
+ It("podman run device host device and container device parameter are directories", func() {
+ SystemExec("mkdir", []string{"/dev/foodevdir"})
+ SystemExec("mknod", []string{"/dev/foodevdir/null", "c", "1", "3"})
+ session := podmanTest.Podman([]string{"run", "-q", "--device", "/dev/foodevdir:/dev/bar", ALPINE, "ls", "/dev/bar/null"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ })
})
diff --git a/transfer.md b/transfer.md
index c2d472f08..eec63d146 100644
--- a/transfer.md
+++ b/transfer.md
@@ -112,6 +112,7 @@ The following podman commands do not have a Docker equivalent:
* [`podman container refresh`](/docs/podman-container-refresh.1.md)
* [`podman container runlabel`](/docs/podman-container-runlabel.1.md)
* [`podman container restore`](/docs/podman-container-restore.1.md)
+* [`podman healthcheck run`](/docs/podman-healthcheck-run.1.md)
* [`podman image exists`](./docs/podman-image-exists.1.md)
* [`podman image sign`](./docs/podman-image-sign.1.md)
* [`podman image trust`](./docs/podman-image-trust.1.md)