Files
diun/internal/provider/common.go
Ian Fijolek 60ddac4a59 Global defaults for image configs
Allows setting of image configs at a global level to act as default
values.

This required a change in the model.Image struct due to a bool field not
having a third, unset state. The remedy is to unmarshal into a temporary
data structure to detect the presents of a field value and then use that
to determine if the default value should be used.

Fixes #491
2023-09-12 12:10:10 -07:00

130 lines
3.7 KiB
Go

package provider
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/containerd/containerd/platforms"
"github.com/crazy-max/diun/v4/internal/model"
"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/imdario/mergo"
)
var (
metadataKeyChars = `a-zA-Z0-9_`
metadataKeyRegexp = regexp.MustCompile(`^[` + metadataKeyChars + `]+$`)
ErrInvalidLabel = errors.New("invalid label error")
)
// ValidateImage returns a standard image through Docker labels
func ValidateImage(image string, metadata, labels map[string]string, watchByDef bool, imageDefaults model.Image) (img model.Image, err error) {
if i := strings.Index(image, "@sha256:"); i > 0 {
image = image[:i]
}
img = model.Image{
Name: image,
}
if err := mergo.Merge(&img, imageDefaults); err != nil {
return img, fmt.Errorf("failed to merge image defaults for image %s", image)
}
if enableStr, ok := labels["diun.enable"]; ok {
enable, err := strconv.ParseBool(enableStr)
if err != nil {
return img, fmt.Errorf("cannot parse %q value of label diun.enable: %w", enableStr, ErrInvalidLabel)
}
if !enable {
return model.Image{}, nil
}
} else if !watchByDef {
return model.Image{}, nil
}
for key, value := range labels {
switch {
case key == "diun.regopt":
img.RegOpt = value
case key == "diun.watch_repo":
if watchRepo, err := strconv.ParseBool(value); err == nil {
img.WatchRepo = &watchRepo
} else {
return img, fmt.Errorf("cannot parse %q value of label %s: %w", value, key, ErrInvalidLabel)
}
case key == "diun.notify_on":
if len(value) == 0 {
break
}
img.NotifyOn = []model.NotifyOn{}
for _, no := range strings.Split(value, ";") {
notifyOn := model.NotifyOn(no)
if !notifyOn.Valid() {
return img, fmt.Errorf("unknown notify status %q: %w", value, ErrInvalidLabel)
}
img.NotifyOn = append(img.NotifyOn, notifyOn)
}
case key == "diun.sort_tags":
if value == "" {
break
}
sortTags := registry.SortTag(value)
if !sortTags.Valid() {
return img, fmt.Errorf("unknown sort tags type %q: %w", value, ErrInvalidLabel)
}
img.SortTags = sortTags
case key == "diun.max_tags":
if img.MaxTags, err = strconv.Atoi(value); err != nil {
return img, fmt.Errorf("cannot parse %q value of label %s: %w", value, key, ErrInvalidLabel)
}
case key == "diun.include_tags":
img.IncludeTags = strings.Split(value, ";")
case key == "diun.exclude_tags":
img.ExcludeTags = strings.Split(value, ";")
case key == "diun.hub_tpl":
img.HubTpl = value
case key == "diun.hub_link":
img.HubLink = value
case key == "diun.platform":
platform, err := platforms.Parse(value)
if err != nil {
return img, fmt.Errorf("cannot parse %q platform of label %s: %w", value, key, ErrInvalidLabel)
}
img.Platform = model.ImagePlatform{
OS: platform.OS,
Arch: platform.Architecture,
Variant: platform.Variant,
}
case strings.HasPrefix(key, "diun.metadata."):
mkey := strings.TrimPrefix(key, "diun.metadata.")
if len(mkey) == 0 || len(value) == 0 {
break
}
if err := validateMetadataKey(mkey); err != nil {
return img, fmt.Errorf("invalid metadata key %q: %w: %w", mkey, err, ErrInvalidLabel)
}
if img.Metadata == nil {
img.Metadata = map[string]string{}
}
img.Metadata[mkey] = value
}
}
// Update provider metadata with metadata from img labels
if err := mergo.Merge(&img.Metadata, metadata); err != nil {
return img, fmt.Errorf("failed merging metadata: %w", err)
}
return img, nil
}
func validateMetadataKey(key string) error {
if !metadataKeyRegexp.MatchString(key) {
return fmt.Errorf("only %q are allowed", metadataKeyChars)
}
return nil
}