diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 15 | ||||
-rw-r--r-- | libpod/healthcheck.go | 92 | ||||
-rw-r--r-- | libpod/image/image.go | 29 | ||||
-rw-r--r-- | libpod/options.go | 12 |
4 files changed, 148 insertions, 0 deletions
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/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/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 + } +} |