mirror of
https://github.com/containers/podman.git
synced 2025-06-26 21:07:02 +08:00
podman healthcheck run (phase 1)
Add the ability to manually run a container's healthcheck command. This is only the first phase of implementing the healthcheck. Subsequent pull requests will deal with the exposing the results and history of healthchecks as well as the scheduling. Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
@ -217,6 +217,10 @@ type PauseValues struct {
|
|||||||
All bool
|
All bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HealthCheckValues struct {
|
||||||
|
PodmanCommand
|
||||||
|
}
|
||||||
|
|
||||||
type KubePlayValues struct {
|
type KubePlayValues struct {
|
||||||
PodmanCommand
|
PodmanCommand
|
||||||
Authfile string
|
Authfile string
|
||||||
|
@ -121,3 +121,10 @@ func getSystemSubCommands() []*cobra.Command {
|
|||||||
_renumberCommand,
|
_renumberCommand,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands that the local client implements
|
||||||
|
func getHealtcheckSubCommands() []*cobra.Command {
|
||||||
|
return []*cobra.Command{
|
||||||
|
_healthcheckrunCommand,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -52,3 +52,8 @@ func getTrustSubCommands() []*cobra.Command {
|
|||||||
func getSystemSubCommands() []*cobra.Command {
|
func getSystemSubCommands() []*cobra.Command {
|
||||||
return []*cobra.Command{}
|
return []*cobra.Command{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Commands that the remoteclient implements
|
||||||
|
func getHealtcheckSubCommands() []*cobra.Command {
|
||||||
|
return []*cobra.Command{}
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/image/manifest"
|
||||||
"github.com/containers/libpod/cmd/podman/cliconfig"
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
||||||
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
"github.com/containers/libpod/cmd/podman/libpodruntime"
|
||||||
"github.com/containers/libpod/cmd/podman/shared"
|
"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) {
|
func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) {
|
||||||
|
var (
|
||||||
|
hasHealthCheck bool
|
||||||
|
healthCheck *manifest.Schema2HealthConfig
|
||||||
|
)
|
||||||
if c.Bool("trace") {
|
if c.Bool("trace") {
|
||||||
span, _ := opentracing.StartSpanFromContext(Ctx, "createContainer")
|
span, _ := opentracing.StartSpanFromContext(Ctx, "createContainer")
|
||||||
defer span.Finish()
|
defer span.Finish()
|
||||||
@ -163,12 +168,32 @@ func createContainer(c *cliconfig.PodmanCommand, runtime *libpod.Runtime) (*libp
|
|||||||
} else {
|
} else {
|
||||||
imageName = newImage.ID()
|
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)
|
createConfig, err := parseCreateOpts(ctx, c, runtime, imageName, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
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)
|
ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
25
cmd/podman/healthcheck.go
Normal file
25
cmd/podman/healthcheck.go
Normal file
@ -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)
|
||||||
|
}
|
53
cmd/podman/healthcheck_run.go
Normal file
53
cmd/podman/healthcheck_run.go
Normal file
@ -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 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
|
||||||
|
}
|
@ -888,6 +888,26 @@ _podman_container_wait() {
|
|||||||
_podman_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() {
|
_podman_generate() {
|
||||||
local boolean_options="
|
local boolean_options="
|
||||||
--help
|
--help
|
||||||
@ -2338,6 +2358,27 @@ _podman_logout() {
|
|||||||
_complete_ "$options_with_args" "$boolean_options"
|
_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() {
|
_podman_generate_kube() {
|
||||||
local options_with_args=""
|
local options_with_args=""
|
||||||
|
|
||||||
@ -2979,6 +3020,7 @@ _podman_podman() {
|
|||||||
exec
|
exec
|
||||||
export
|
export
|
||||||
generate
|
generate
|
||||||
|
healthcheck
|
||||||
history
|
history
|
||||||
image
|
image
|
||||||
images
|
images
|
||||||
|
39
docs/podman-healthcheck-run.1.md
Normal file
39
docs/podman-healthcheck-run.1.md
Normal file
@ -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>
|
22
docs/podman-healthcheck.1.md
Normal file
22
docs/podman-healthcheck.1.md
Normal file
@ -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>
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
cnitypes "github.com/containernetworking/cni/pkg/types/current"
|
||||||
|
"github.com/containers/image/manifest"
|
||||||
"github.com/containers/libpod/libpod/lock"
|
"github.com/containers/libpod/libpod/lock"
|
||||||
"github.com/containers/libpod/pkg/namespaces"
|
"github.com/containers/libpod/pkg/namespaces"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
@ -365,6 +366,9 @@ type ContainerConfig struct {
|
|||||||
|
|
||||||
// Systemd tells libpod to setup the container in systemd mode
|
// Systemd tells libpod to setup the container in systemd mode
|
||||||
Systemd bool `json:"systemd"`
|
Systemd bool `json:"systemd"`
|
||||||
|
|
||||||
|
// HealtchCheckConfig has the health check command and related timings
|
||||||
|
HealthCheckConfig *manifest.Schema2HealthConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerStatus returns a string representation for users
|
// ContainerStatus returns a string representation for users
|
||||||
@ -1085,3 +1089,14 @@ func (c *Container) ContainerState() (*ContainerState, error) {
|
|||||||
deepcopier.Copy(c.state).To(returnConfig)
|
deepcopier.Copy(c.state).To(returnConfig)
|
||||||
return c.state, nil
|
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
|
||||||
|
}
|
||||||
|
92
libpod/healthcheck.go
Normal file
92
libpod/healthcheck.go
Normal file
@ -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
|
||||||
|
}
|
@ -1151,3 +1151,32 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag
|
|||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/image/manifest"
|
||||||
"github.com/containers/libpod/pkg/namespaces"
|
"github.com/containers/libpod/pkg/namespaces"
|
||||||
"github.com/containers/storage"
|
"github.com/containers/storage"
|
||||||
"github.com/containers/storage/pkg/idtools"
|
"github.com/containers/storage/pkg/idtools"
|
||||||
@ -1469,3 +1470,14 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
|
|||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -332,3 +332,8 @@ func IsImageNotFound(err error) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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])
|
||||||
|
}
|
||||||
|
@ -746,3 +746,8 @@ func IsImageNotFound(err error) bool {
|
|||||||
}
|
}
|
||||||
return false
|
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
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containers/image/manifest"
|
||||||
"github.com/containers/libpod/libpod"
|
"github.com/containers/libpod/libpod"
|
||||||
"github.com/containers/libpod/pkg/namespaces"
|
"github.com/containers/libpod/pkg/namespaces"
|
||||||
"github.com/containers/libpod/pkg/rootless"
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
@ -86,6 +87,8 @@ type CreateConfig struct {
|
|||||||
Env map[string]string //env
|
Env map[string]string //env
|
||||||
ExposedPorts map[nat.Port]struct{}
|
ExposedPorts map[nat.Port]struct{}
|
||||||
GroupAdd []string // group-add
|
GroupAdd []string // group-add
|
||||||
|
HasHealthCheck bool
|
||||||
|
HealthCheck *manifest.Schema2HealthConfig
|
||||||
HostAdd []string //add-host
|
HostAdd []string //add-host
|
||||||
Hostname string //hostname
|
Hostname string //hostname
|
||||||
Image string
|
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
|
// Always use a cleanup process to clean up Podman after termination
|
||||||
options = append(options, libpod.WithExitCommand(c.createExitCommand()))
|
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
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,4 +6,5 @@ var (
|
|||||||
ALPINE = "docker.io/library/alpine:latest"
|
ALPINE = "docker.io/library/alpine:latest"
|
||||||
infra = "k8s.gcr.io/pause:3.1"
|
infra = "k8s.gcr.io/pause:3.1"
|
||||||
BB = "docker.io/library/busybox:latest"
|
BB = "docker.io/library/busybox:latest"
|
||||||
|
healthcheck = "docker.io/libpod/alpine_healthcheck:latest"
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ package integration
|
|||||||
var (
|
var (
|
||||||
STORAGE_OPTIONS = "--storage-driver vfs"
|
STORAGE_OPTIONS = "--storage-driver vfs"
|
||||||
ROOTLESS_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"
|
nginx = "quay.io/libpod/alpine_nginx:latest"
|
||||||
BB_GLIBC = "docker.io/library/busybox:glibc"
|
BB_GLIBC = "docker.io/library/busybox:glibc"
|
||||||
registry = "docker.io/library/registry:2"
|
registry = "docker.io/library/registry:2"
|
||||||
|
85
test/e2e/healthcheck_run_test.go
Normal file
85
test/e2e/healthcheck_run_test.go
Normal file
@ -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))
|
||||||
|
})
|
||||||
|
})
|
@ -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 refresh`](/docs/podman-container-refresh.1.md)
|
||||||
* [`podman container runlabel`](/docs/podman-container-runlabel.1.md)
|
* [`podman container runlabel`](/docs/podman-container-runlabel.1.md)
|
||||||
* [`podman container restore`](/docs/podman-container-restore.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 exists`](./docs/podman-image-exists.1.md)
|
||||||
* [`podman image sign`](./docs/podman-image-sign.1.md)
|
* [`podman image sign`](./docs/podman-image-sign.1.md)
|
||||||
* [`podman image trust`](./docs/podman-image-trust.1.md)
|
* [`podman image trust`](./docs/podman-image-trust.1.md)
|
||||||
|
Reference in New Issue
Block a user