Skip to content

Commit

Permalink
Global defaults for image configs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
IamTheFij committed Sep 12, 2023
1 parent 052a472 commit 60ddac4
Show file tree
Hide file tree
Showing 20 changed files with 1,010 additions and 71 deletions.
12 changes: 6 additions & 6 deletions internal/app/diun.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,32 +153,32 @@ func (di *Diun) Run() {
defer di.pool.Release()

// Docker provider
for _, job := range dockerPrd.New(di.cfg.Providers.Docker).ListJob() {
for _, job := range dockerPrd.New(di.cfg.Providers.Docker, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

// Swarm provider
for _, job := range swarmPrd.New(di.cfg.Providers.Swarm).ListJob() {
for _, job := range swarmPrd.New(di.cfg.Providers.Swarm, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

// Kubernetes provider
for _, job := range kubernetesPrd.New(di.cfg.Providers.Kubernetes).ListJob() {
for _, job := range kubernetesPrd.New(di.cfg.Providers.Kubernetes, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

// File provider
for _, job := range filePrd.New(di.cfg.Providers.File).ListJob() {
for _, job := range filePrd.New(di.cfg.Providers.File, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

// Dockerfile provider
for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() {
for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

// Nomad provider
for _, job := range nomadPrd.New(di.cfg.Providers.Nomad).ListJob() {
for _, job := range nomadPrd.New(di.cfg.Providers.Nomad, di.cfg.Watch.ImageDefaults).ListJob() {
di.createJob(job)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/app/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (di *Diun) createJob(job model.Job) {
// Set defaults
if err := mergo.Merge(&job.Image, model.Image{
Platform: model.ImagePlatform{},
WatchRepo: false,
WatchRepo: utl.NewFalse(),
MaxTags: 0,
}); err != nil {
sublog.Error().Err(err).Msg("Cannot set default values")
Expand Down Expand Up @@ -118,7 +118,7 @@ func (di *Diun) createJob(job model.Job) {
sublog.Error().Err(err).Msgf("Invoking job")
}

if !job.Image.WatchRepo || len(job.RegImage.Domain) == 0 {
if job.Image.WatchRepo == nil || !*job.Image.WatchRepo || len(job.RegImage.Domain) == 0 {
return
}

Expand Down
5 changes: 5 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/crazy-max/diun/v4/internal/config"
"github.com/crazy-max/diun/v4/internal/model"
"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/crazy-max/diun/v4/pkg/utl"
"github.com/crazy-max/gonfig/env"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -58,6 +59,10 @@ func TestLoadFile(t *testing.T) {
BaseURL: "https://hc-ping.com/",
UUID: "5bf66975-d4c7-4bf5-bcc8-b8d8a82ea278",
},
ImageDefaults: &model.Image{
NotifyOn: model.NotifyOnDefaults,
SortTags: registry.SortTagReverse,
},
},
Notif: &model.Notif{
Amqp: &model.NotifAmqp{
Expand Down
6 changes: 4 additions & 2 deletions internal/model/image.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package model

import "github.com/crazy-max/diun/v4/pkg/registry"
import (
"github.com/crazy-max/diun/v4/pkg/registry"
)

// Image holds image configuration
type Image struct {
Name string `yaml:"name,omitempty" json:",omitempty"`
Platform ImagePlatform `yaml:"platform,omitempty" json:",omitempty"`
RegOpt string `yaml:"regopt,omitempty" json:",omitempty"`
WatchRepo bool `yaml:"watch_repo,omitempty" json:",omitempty"`
WatchRepo *bool `yaml:"watch_repo,omitempty" json:",omitempty"`
NotifyOn []NotifyOn `yaml:"notify_on,omitempty" json:",omitempty"`
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
SortTags registry.SortTag `yaml:"sort_tags,omitempty" json:",omitempty"`
Expand Down
6 changes: 6 additions & 0 deletions internal/model/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package model
import (
"time"

"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/crazy-max/diun/v4/pkg/utl"
)

Expand All @@ -15,6 +16,7 @@ type Watch struct {
RunOnStartup *bool `yaml:"runOnStartup,omitempty" json:"runOnStartup,omitempty" validate:"required"`
CompareDigest *bool `yaml:"compareDigest,omitempty" json:"compareDigest,omitempty" validate:"required"`
Healthchecks *Healthchecks `yaml:"healthchecks,omitempty" json:"healthchecks,omitempty"`
ImageDefaults *Image `yaml:"defaults,omitempty" json:"defaults,omitempty"`
}

// GetDefaults gets the default values
Expand All @@ -31,4 +33,8 @@ func (s *Watch) SetDefaults() {
s.FirstCheckNotif = utl.NewFalse()
s.RunOnStartup = utl.NewTrue()
s.CompareDigest = utl.NewTrue()
s.ImageDefaults = &Image{
NotifyOn: NotifyOnDefaults,
SortTags: registry.SortTagReverse,
}
}
41 changes: 26 additions & 15 deletions internal/provider/common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package provider

import (
"errors"
"fmt"
"regexp"
"strconv"
Expand All @@ -9,30 +10,33 @@ import (
"github.com/containerd/containerd/platforms"
"github.com/crazy-max/diun/v4/internal/model"
"github.com/crazy-max/diun/v4/pkg/registry"
"github.com/pkg/errors"
"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) (img model.Image, err error) {
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,
Metadata: metadata,
NotifyOn: model.NotifyOnDefaults,
SortTags: registry.SortTagReverse,
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 %s value of label diun.enable", enableStr)
return img, fmt.Errorf("cannot parse %q value of label diun.enable: %w", enableStr, ErrInvalidLabel)
}
if !enable {
return model.Image{}, nil
Expand All @@ -46,8 +50,10 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
case key == "diun.regopt":
img.RegOpt = value
case key == "diun.watch_repo":
if img.WatchRepo, err = strconv.ParseBool(value); err != nil {
return img, fmt.Errorf("cannot parse %s value of label %s", value, key)
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 {
Expand All @@ -57,7 +63,7 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
for _, no := range strings.Split(value, ";") {
notifyOn := model.NotifyOn(no)
if !notifyOn.Valid() {
return img, fmt.Errorf("unknown notify status %q", value)
return img, fmt.Errorf("unknown notify status %q: %w", value, ErrInvalidLabel)
}
img.NotifyOn = append(img.NotifyOn, notifyOn)
}
Expand All @@ -67,12 +73,12 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
}
sortTags := registry.SortTag(value)
if !sortTags.Valid() {
return img, fmt.Errorf("unknown sort tags type %q", value)
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 %s value of label %s", value, key)
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, ";")
Expand All @@ -85,7 +91,7 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
case key == "diun.platform":
platform, err := platforms.Parse(value)
if err != nil {
return img, fmt.Errorf("cannot parse %s platform of label %s", value, key)
return img, fmt.Errorf("cannot parse %q platform of label %s: %w", value, key, ErrInvalidLabel)
}
img.Platform = model.ImagePlatform{
OS: platform.OS,
Expand All @@ -98,7 +104,7 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
break
}
if err := validateMetadataKey(mkey); err != nil {
return img, errors.Wrapf(err, "invalid metadata key %q", mkey)
return img, fmt.Errorf("invalid metadata key %q: %w: %w", mkey, err, ErrInvalidLabel)
}
if img.Metadata == nil {
img.Metadata = map[string]string{}
Expand All @@ -107,12 +113,17 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
}
}

// 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 errors.Errorf("only %q are allowed", metadataKeyChars)
return fmt.Errorf("only %q are allowed", metadataKeyChars)
}
return nil
}
Loading

0 comments on commit 60ddac4

Please sign in to comment.