fix(lifecycle): cleanup lifecycle
- removes unwieldy SkipUpdate return value in favor of errors.Is - generalizes the code for all four phases - allows timeout to be defined for all phases - enables explicit unit in timeout label values (in addition to implicit minutes)refactor-update
parent
76f9cea516
commit
023c1a7d93
@ -0,0 +1,15 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseDuration parses the input string as a duration, treating a plain number as implicitly using the specified unit
|
||||||
|
func ParseDuration(input string, unitlessUnit time.Duration) (time.Duration, error) {
|
||||||
|
if unitless, err := strconv.Atoi(input); err == nil {
|
||||||
|
return unitlessUnit * time.Duration(unitless), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.ParseDuration(input)
|
||||||
|
}
|
||||||
@ -1,67 +1,92 @@
|
|||||||
package container
|
package container
|
||||||
|
|
||||||
import "strconv"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containrrr/watchtower/internal/util"
|
||||||
|
wt "github.com/containrrr/watchtower/pkg/types"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
watchtowerLabel = "com.centurylinklabs.watchtower"
|
namespace = "com.centurylinklabs.watchtower"
|
||||||
signalLabel = "com.centurylinklabs.watchtower.stop-signal"
|
watchtowerLabel = namespace
|
||||||
enableLabel = "com.centurylinklabs.watchtower.enable"
|
signalLabel = namespace + ".stop-signal"
|
||||||
monitorOnlyLabel = "com.centurylinklabs.watchtower.monitor-only"
|
enableLabel = namespace + ".enable"
|
||||||
noPullLabel = "com.centurylinklabs.watchtower.no-pull"
|
monitorOnlyLabel = namespace + ".monitor-only"
|
||||||
dependsOnLabel = "com.centurylinklabs.watchtower.depends-on"
|
noPullLabel = namespace + ".no-pull"
|
||||||
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
dependsOnLabel = namespace + ".depends-on"
|
||||||
scope = "com.centurylinklabs.watchtower.scope"
|
zodiacLabel = "com.centurylinklabs.zodiac.original-image"
|
||||||
preCheckLabel = "com.centurylinklabs.watchtower.lifecycle.pre-check"
|
scope = namespace + ".scope"
|
||||||
postCheckLabel = "com.centurylinklabs.watchtower.lifecycle.post-check"
|
|
||||||
preUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update"
|
|
||||||
postUpdateLabel = "com.centurylinklabs.watchtower.lifecycle.post-update"
|
|
||||||
preUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.pre-update-timeout"
|
|
||||||
postUpdateTimeoutLabel = "com.centurylinklabs.watchtower.lifecycle.post-update-timeout"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetLifecyclePreCheckCommand returns the pre-check command set in the container metadata or an empty string
|
// ContainsWatchtowerLabel takes a map of labels and values and tells
|
||||||
func (c Container) GetLifecyclePreCheckCommand() string {
|
// the consumer whether it contains a valid watchtower instance label
|
||||||
return c.getLabelValueOrEmpty(preCheckLabel)
|
func ContainsWatchtowerLabel(labels map[string]string) bool {
|
||||||
|
val, ok := labels[watchtowerLabel]
|
||||||
|
return ok && val == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetLifecyclePostCheckCommand returns the post-check command set in the container metadata or an empty string
|
// GetLifecycleCommand returns the lifecycle command set in the container metadata or an empty string
|
||||||
func (c Container) GetLifecyclePostCheckCommand() string {
|
func (c *Container) GetLifecycleCommand(phase wt.LifecyclePhase) string {
|
||||||
return c.getLabelValueOrEmpty(postCheckLabel)
|
label := fmt.Sprintf("%v.lifecycle.%v", namespace, phase)
|
||||||
}
|
value, found := c.getLabelValue(label)
|
||||||
|
|
||||||
// GetLifecyclePreUpdateCommand returns the pre-update command set in the container metadata or an empty string
|
if !found {
|
||||||
func (c Container) GetLifecyclePreUpdateCommand() string {
|
return ""
|
||||||
return c.getLabelValueOrEmpty(preUpdateLabel)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// GetLifecyclePostUpdateCommand returns the post-update command set in the container metadata or an empty string
|
return value
|
||||||
func (c Container) GetLifecyclePostUpdateCommand() string {
|
|
||||||
return c.getLabelValueOrEmpty(postUpdateLabel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainsWatchtowerLabel takes a map of labels and values and tells
|
// GetLifecycleTimeout checks whether a container has a specific timeout set
|
||||||
// the consumer whether it contains a valid watchtower instance label
|
// for how long the lifecycle command is allowed to run. This value is expressed
|
||||||
func ContainsWatchtowerLabel(labels map[string]string) bool {
|
// either as a duration, an integer (minutes implied), or as 0 which will allow the command/script
|
||||||
val, ok := labels[watchtowerLabel]
|
// to run indefinitely. Users should be cautious with the 0 option, as that
|
||||||
return ok && val == "true"
|
// could result in watchtower waiting forever.
|
||||||
|
func (c *Container) GetLifecycleTimeout(phase wt.LifecyclePhase) time.Duration {
|
||||||
|
label := fmt.Sprintf("%v.lifecycle.%v-timeout", namespace, phase)
|
||||||
|
timeout, err := c.getDurationLabelValue(label, time.Minute)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
timeout = time.Minute
|
||||||
|
if !errors.Is(err, errorLabelNotFound) {
|
||||||
|
logrus.WithError(err).Errorf("could not parse timeout label value for %v lifecycle", phase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Container) getLabelValueOrEmpty(label string) string {
|
func (c *Container) getLabelValueOrEmpty(label string) string {
|
||||||
if val, ok := c.containerInfo.Config.Labels[label]; ok {
|
if val, ok := c.containerInfo.Config.Labels[label]; ok {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Container) getLabelValue(label string) (string, bool) {
|
func (c *Container) getLabelValue(label string) (string, bool) {
|
||||||
val, ok := c.containerInfo.Config.Labels[label]
|
val, ok := c.containerInfo.Config.Labels[label]
|
||||||
return val, ok
|
return val, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Container) getBoolLabelValue(label string) (bool, error) {
|
func (c *Container) getBoolLabelValue(label string) (bool, error) {
|
||||||
if strVal, ok := c.containerInfo.Config.Labels[label]; ok {
|
if strVal, ok := c.containerInfo.Config.Labels[label]; ok {
|
||||||
value, err := strconv.ParseBool(strVal)
|
value, err := strconv.ParseBool(strVal)
|
||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
return false, errorLabelNotFound
|
return false, errorLabelNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) getDurationLabelValue(label string, unitlessUnit time.Duration) (time.Duration, error) {
|
||||||
|
value, found := c.getLabelValue(label)
|
||||||
|
if !found || len(value) < 1 {
|
||||||
|
return 0, errorLabelNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.ParseDuration(value, unitlessUnit)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,27 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type LifecyclePhase int
|
||||||
|
|
||||||
|
const (
|
||||||
|
PreCheck LifecyclePhase = iota
|
||||||
|
PreUpdate
|
||||||
|
PostUpdate
|
||||||
|
PostCheck
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p LifecyclePhase) String() string {
|
||||||
|
switch p {
|
||||||
|
case PreCheck:
|
||||||
|
return "pre-check"
|
||||||
|
case PreUpdate:
|
||||||
|
return "pre-update"
|
||||||
|
case PostUpdate:
|
||||||
|
return "post-update"
|
||||||
|
case PostCheck:
|
||||||
|
return "post-check"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("invalid(%d)", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue