Files
podman/libpod/healthcheck_config.go
Jan Rodák a1249425bd Configure HealthCheck with podman update
New flags in a `podman update` can change the configuration of HealthCheck when the container is started, without having to restart or recreate the container.

This can help determine why a given container suddenly started failing HealthCheck without interfering with the services it provides. For example, reconfigure HealthCheck to keep logs longer than the usual last X results, store logs to other destinations, etc.

Fixes: https://issues.redhat.com/browse/RHEL-60561

Signed-off-by: Jan Rodák <hony.com@seznam.cz>
2024-11-19 19:44:14 +01:00

166 lines
5.4 KiB
Go

//go:build !remote
package libpod
import (
"errors"
"strings"
"time"
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/specgenutil"
)
type HealthCheckConfig struct {
*manifest.Schema2HealthConfig
}
type StartupHealthCheckConfig struct {
*define.StartupHealthCheck
}
type IHealthCheckConfig interface {
SetTo(config *ContainerConfig)
IsStartup() bool
IsNil() bool
IsTimeChanged(oldInterval time.Duration) bool
GetInterval() time.Duration
SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions)
IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool
SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool
}
func (h *HealthCheckConfig) SetTo(config *ContainerConfig) {
config.HealthCheckConfig = h.Schema2HealthConfig
}
func (h *StartupHealthCheckConfig) SetTo(config *ContainerConfig) {
config.StartupHealthCheckConfig = h.StartupHealthCheck
}
func (h *HealthCheckConfig) IsNil() bool {
return h.Schema2HealthConfig == nil
}
func (h *StartupHealthCheckConfig) IsNil() bool {
return h.StartupHealthCheck == nil
}
func (h *HealthCheckConfig) IsStartup() bool {
return false
}
func (h *StartupHealthCheckConfig) IsStartup() bool {
return true
}
func (h *HealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool {
return h.Interval != oldInterval
}
func (h *StartupHealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool {
return h.Interval != oldInterval
}
func (h *HealthCheckConfig) GetInterval() time.Duration {
return h.Interval
}
func (h *StartupHealthCheckConfig) GetInterval() time.Duration {
return h.Interval
}
func (h *HealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) {
healthCheckOptions.Cmd = strings.Join(h.Test, " ")
healthCheckOptions.Interval = h.Interval.String()
healthCheckOptions.Retries = h.Retries
healthCheckOptions.Timeout = h.Timeout.String()
healthCheckOptions.StartPeriod = h.StartPeriod.String()
}
func (h *StartupHealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) {
healthCheckOptions.Cmd = strings.Join(h.Test, " ")
healthCheckOptions.Interval = h.Interval.String()
healthCheckOptions.Retries = h.Retries
healthCheckOptions.Timeout = h.Timeout.String()
healthCheckOptions.Successes = h.Successes
}
func (h *HealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool {
return updateHealthCheckConfig.IsHealthCheckCommandSet(h.Schema2HealthConfig)
}
func (h *StartupHealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool {
return updateHealthCheckConfig.IsStartupHealthCheckCommandSet(h.StartupHealthCheck)
}
func (h *HealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool {
return updateHealthCheckConfig.SetNewHealthCheckConfigTo(healthCheckOptions)
}
func (h *StartupHealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool {
return updateHealthCheckConfig.SetNewStartupHealthCheckConfigTo(healthCheckOptions)
}
func GetNewHealthCheckConfig(originalHealthCheckConfig IHealthCheckConfig, updateHealthCheckConfig define.UpdateHealthCheckConfig) (IHealthCheckConfig, bool, error) {
if originalHealthCheckConfig.IsHealthCheckCommandSet(updateHealthCheckConfig) {
return nil, false, errors.New("startup healthcheck command is not set")
}
healthCheckOptions := define.HealthCheckOptions{
Cmd: "",
Interval: define.DefaultHealthCheckInterval,
Retries: int(define.DefaultHealthCheckRetries),
Timeout: define.DefaultHealthCheckTimeout,
StartPeriod: define.DefaultHealthCheckStartPeriod,
Successes: 0,
}
if originalHealthCheckConfig.IsStartup() {
healthCheckOptions.Retries = 0
}
if !originalHealthCheckConfig.IsNil() {
originalHealthCheckConfig.SetCurrentConfigTo(&healthCheckOptions)
}
noHealthCheck := false
if updateHealthCheckConfig.NoHealthCheck != nil {
noHealthCheck = *updateHealthCheckConfig.NoHealthCheck
}
changed := originalHealthCheckConfig.SetNewHealthCheckOptions(updateHealthCheckConfig, &healthCheckOptions)
if noHealthCheck && changed {
return nil, false, errors.New("cannot specify both --no-healthcheck and other HealthCheck flags")
}
if noHealthCheck {
if originalHealthCheckConfig.IsStartup() {
return &StartupHealthCheckConfig{StartupHealthCheck: nil}, true, nil
}
return &HealthCheckConfig{Schema2HealthConfig: &manifest.Schema2HealthConfig{Test: []string{"NONE"}}}, true, nil
}
newHealthCheckConfig, err := specgenutil.MakeHealthCheckFromCli(
healthCheckOptions.Cmd,
healthCheckOptions.Interval,
uint(healthCheckOptions.Retries),
healthCheckOptions.Timeout,
healthCheckOptions.StartPeriod,
true,
)
if err != nil {
return nil, false, err
}
if originalHealthCheckConfig.IsStartup() {
newStartupHealthCheckConfig := new(define.StartupHealthCheck)
newStartupHealthCheckConfig.Schema2HealthConfig = *newHealthCheckConfig
newStartupHealthCheckConfig.Successes = healthCheckOptions.Successes
return &StartupHealthCheckConfig{StartupHealthCheck: newStartupHealthCheckConfig}, changed, nil
}
return &HealthCheckConfig{Schema2HealthConfig: newHealthCheckConfig}, changed, nil
}