Skip to content

Commit 713cfa4

Browse files
committed
Add semver option to watch_repo
This will fetch the list of all tags from the repo, but exclude any that are a lower semver than the current tag. This also requires sort_tags to be semver as well.
1 parent 931eb3c commit 713cfa4

File tree

11 files changed

+142
-65
lines changed

11 files changed

+142
-65
lines changed

docs/faq.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ regopts:
203203
Or you can tweak the [`schedule` setting](config/watch.md#schedule) with something like `0 */6 * * *` (every 6 hours).
204204

205205
!!! warning
206-
Also be careful with the `watch_repo` setting as it will fetch manifest for **ALL** tags available for the image.
206+
Also be careful with the `watch_repo` setting as it will fetch manifest for **ALL** tags available for the image if set to true. If using semver sorting, you can set `watch_repo` to semver and it will instead only watch images that are newer versions than the current image.
207207

208208
## Tags sorting when using `watch_repo`
209209

internal/app/job.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func (di *Diun) createJob(job model.Job) {
6363
// Set defaults
6464
if err := mergo.Merge(&job.Image, model.Image{
6565
Platform: model.ImagePlatform{},
66-
WatchRepo: utl.NewFalse(),
66+
WatchRepo: model.WatchRepoNo,
6767
MaxTags: 0,
6868
}); err != nil {
6969
sublog.Error().Err(err).Msg("Cannot set default values")
@@ -118,7 +118,7 @@ func (di *Diun) createJob(job model.Job) {
118118
sublog.Error().Err(err).Msgf("Invoking job")
119119
}
120120

121-
if !*job.Image.WatchRepo || len(job.RegImage.Domain) == 0 {
121+
if job.Image.WatchRepo == model.WatchRepoNo || len(job.RegImage.Domain) == 0 {
122122
return
123123
}
124124

internal/config/config_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestLoadFile(t *testing.T) {
6060
},
6161
},
6262
Defaults: &model.Defaults{
63-
WatchRepo: utl.NewFalse(),
63+
WatchRepo: model.WatchRepoNo,
6464
NotifyOn: []model.NotifyOn{model.NotifyOnNew},
6565
MaxTags: 5,
6666
SortTags: registry.SortTagReverse,

internal/model/defaults.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ package model
22

33
import (
44
"github.com/crazy-max/diun/v4/pkg/registry"
5-
"github.com/crazy-max/diun/v4/pkg/utl"
65
)
76

87
// Defaults holds data necessary for image defaults configuration
98
type Defaults struct {
10-
WatchRepo *bool `yaml:"watchRepo,omitempty" json:"watchRepo,omitempty"`
9+
WatchRepo WatchRepo `yaml:"watchRepo,omitempty" json:"watchRepo,omitempty"`
1110
NotifyOn []NotifyOn `yaml:"notifyOn,omitempty" json:"notifyOn,omitempty"`
1211
MaxTags int `yaml:"maxTags,omitempty" json:"maxTags,omitempty"`
1312
SortTags registry.SortTag `yaml:"sortTags,omitempty" json:"sortTags,omitempty"`
@@ -25,7 +24,7 @@ func (s *Defaults) GetDefaults() *Defaults {
2524

2625
// SetDefaults sets the default values
2726
func (s *Defaults) SetDefaults() {
28-
s.WatchRepo = utl.NewFalse()
27+
s.WatchRepo = WatchRepoNo
2928
s.NotifyOn = NotifyOnDefaults
3029
s.SortTags = registry.SortTagReverse
3130
}

internal/model/image.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package model
22

33
import (
4+
"slices"
5+
46
"github.com/crazy-max/diun/v4/pkg/registry"
57
)
68

@@ -9,7 +11,7 @@ type Image struct {
911
Name string `yaml:"name,omitempty" json:",omitempty"`
1012
Platform ImagePlatform `yaml:"platform,omitempty" json:",omitempty"`
1113
RegOpt string `yaml:"regopt,omitempty" json:",omitempty"`
12-
WatchRepo *bool `yaml:"watch_repo,omitempty" json:",omitempty"`
14+
WatchRepo WatchRepo `yaml:"watch_repo,omitempty" json:",omitempty"`
1315
NotifyOn []NotifyOn `yaml:"notify_on,omitempty" json:",omitempty"`
1416
MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"`
1517
SortTags registry.SortTag `yaml:"sort_tags,omitempty" json:",omitempty"`
@@ -27,6 +29,25 @@ type ImagePlatform struct {
2729
Variant string `yaml:"variant,omitempty" json:",omitempty"`
2830
}
2931

32+
// WatchRepo constants
33+
const (
34+
WatchRepoNo = WatchRepo("false")
35+
WatchRepoAll = WatchRepo("true")
36+
WatchRepoSemver = WatchRepo("semver")
37+
)
38+
39+
// Valid checks watch repo is valid
40+
func (w WatchRepo) Valid() bool {
41+
return slices.Contains([]WatchRepo{
42+
WatchRepoNo,
43+
WatchRepoAll,
44+
WatchRepoSemver,
45+
}, w)
46+
}
47+
48+
// WatchRepo holds repo watching intent
49+
type WatchRepo string
50+
3051
// ImageStatus constants
3152
const (
3253
ImageStatusNew = ImageStatus("new")

internal/provider/common.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,14 @@ func ValidateImage(image string, metadata, labels map[string]string, watchByDef
5151
img.RegOpt = value
5252
case key == "diun.watch_repo":
5353
if watchRepo, err := strconv.ParseBool(value); err == nil {
54-
img.WatchRepo = &watchRepo
54+
// If boolean value, coerce it into true/false
55+
img.WatchRepo = model.WatchRepo(strconv.FormatBool(watchRepo))
5556
} else {
56-
return img, errors.Wrapf(err, "cannot parse %q value of label %s", value, key)
57+
// Otherwise use it directly
58+
img.WatchRepo = model.WatchRepo(value)
59+
}
60+
if !img.WatchRepo.Valid() {
61+
return img, errors.Errorf(`invalid value of watch_repo %q`, value)
5762
}
5863
case key == "diun.notify_on":
5964
if len(value) == 0 {

internal/provider/common_test.go

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
"github.com/crazy-max/diun/v4/internal/model"
77
"github.com/crazy-max/diun/v4/pkg/registry"
8-
"github.com/crazy-max/diun/v4/pkg/utl"
98
"github.com/pkg/errors"
109
"github.com/stretchr/testify/require"
1110
)
@@ -111,11 +110,11 @@ func TestValidateImage(t *testing.T) {
111110
image: "myimg",
112111
watchByDef: true,
113112
defaults: &model.Defaults{
114-
WatchRepo: utl.NewTrue(),
113+
WatchRepo: model.WatchRepoAll,
115114
},
116115
expectedImage: model.Image{
117116
Name: "myimg",
118-
WatchRepo: utl.NewTrue(),
117+
WatchRepo: model.WatchRepoAll,
119118
},
120119
expectedErr: nil,
121120
},
@@ -130,7 +129,7 @@ func TestValidateImage(t *testing.T) {
130129
expectedImage: model.Image{
131130
Name: "myimg",
132131
},
133-
expectedErr: errors.New(`cannot parse "chickens" value of label diun.watch_repo`),
132+
expectedErr: errors.New(`invalid value of watch_repo "chickens"`),
134133
},
135134
{
136135
name: "Override default image values with labels (true > false)",
@@ -140,27 +139,43 @@ func TestValidateImage(t *testing.T) {
140139
"diun.watch_repo": "false",
141140
},
142141
defaults: &model.Defaults{
143-
WatchRepo: utl.NewTrue(),
142+
WatchRepo: model.WatchRepoAll,
144143
},
145144
expectedImage: model.Image{
146145
Name: "myimg",
147-
WatchRepo: utl.NewFalse(),
146+
WatchRepo: model.WatchRepoNo,
148147
},
149148
expectedErr: nil,
150149
},
151150
{
152-
name: "Override default image values with labels (false > true): invalid label error",
151+
name: "Override default image values with labels (false > true)",
153152
image: "myimg",
154153
watchByDef: true,
155154
labels: map[string]string{
156155
"diun.watch_repo": "true",
157156
},
158157
defaults: &model.Defaults{
159-
WatchRepo: utl.NewFalse(),
158+
WatchRepo: model.WatchRepoNo,
160159
},
161160
expectedImage: model.Image{
162161
Name: "myimg",
163-
WatchRepo: utl.NewTrue(),
162+
WatchRepo: model.WatchRepoAll,
163+
},
164+
expectedErr: nil,
165+
},
166+
{
167+
name: "Override default image values with labels (false > semver)",
168+
image: "myimg",
169+
watchByDef: true,
170+
labels: map[string]string{
171+
"diun.watch_repo": "semver",
172+
},
173+
defaults: &model.Defaults{
174+
WatchRepo: model.WatchRepoNo,
175+
},
176+
expectedImage: model.Image{
177+
Name: "myimg",
178+
WatchRepo: model.WatchRepoSemver,
164179
},
165180
expectedErr: nil,
166181
},

internal/provider/file/file_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
"github.com/crazy-max/diun/v4/internal/model"
77
"github.com/crazy-max/diun/v4/pkg/registry"
8-
"github.com/crazy-max/diun/v4/pkg/utl"
98
"github.com/stretchr/testify/assert"
109
)
1110

@@ -42,7 +41,7 @@ var (
4241
Provider: "file",
4342
Image: model.Image{
4443
Name: "docker.bintray.io/jfrog/xray-server:2.8.6",
45-
WatchRepo: utl.NewTrue(),
44+
WatchRepo: model.WatchRepoAll,
4645
NotifyOn: []model.NotifyOn{
4746
model.NotifyOnNew,
4847
},
@@ -78,7 +77,7 @@ var (
7877
Provider: "file",
7978
Image: model.Image{
8079
Name: "crazymax/swarm-cronjob",
81-
WatchRepo: utl.NewTrue(),
80+
WatchRepo: model.WatchRepoAll,
8281
NotifyOn: model.NotifyOnDefaults,
8382
SortTags: registry.SortTagSemver,
8483
MaxTags: 25,
@@ -94,7 +93,7 @@ var (
9493
Provider: "file",
9594
Image: model.Image{
9695
Name: "docker.io/portainer/portainer",
97-
WatchRepo: utl.NewTrue(),
96+
WatchRepo: model.WatchRepoAll,
9897
NotifyOn: model.NotifyOnDefaults,
9998
MaxTags: 10,
10099
SortTags: registry.SortTagReverse,
@@ -110,7 +109,7 @@ var (
110109
Provider: "file",
111110
Image: model.Image{
112111
Name: "traefik",
113-
WatchRepo: utl.NewTrue(),
112+
WatchRepo: model.WatchRepoAll,
114113
NotifyOn: model.NotifyOnDefaults,
115114
SortTags: registry.SortTagDefault,
116115
MaxTags: 25,
@@ -176,7 +175,7 @@ var (
176175
Provider: "file",
177176
Image: model.Image{
178177
Name: "crazymax/ddns-route53",
179-
WatchRepo: utl.NewTrue(),
178+
WatchRepo: model.WatchRepoAll,
180179
NotifyOn: model.NotifyOnDefaults,
181180
SortTags: registry.SortTagReverse,
182181
MaxTags: 25,
@@ -261,10 +260,10 @@ func TestDefaultImageOptions(t *testing.T) {
261260
fc := New(&model.PrdFile{
262261
Filename: "./fixtures/dockerhub.yml",
263262
}, &model.Defaults{
264-
WatchRepo: utl.NewTrue(),
263+
WatchRepo: model.WatchRepoAll,
265264
})
266265

267266
for _, job := range fc.ListJob() {
268-
assert.True(t, *job.Image.WatchRepo)
267+
assert.Equal(t, model.WatchRepoAll, job.Image.WatchRepo)
269268
}
270269
}

internal/provider/file/image.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func (c *Client) listFileImage() []model.Image {
3333

3434
for _, item := range items {
3535
// Set default WatchRepo
36-
if item.WatchRepo == nil {
36+
if item.WatchRepo == "" {
3737
item.WatchRepo = c.defaults.WatchRepo
3838
}
3939
// Check NotifyOn

pkg/registry/tags.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ type Tags struct {
2222

2323
// TagsOptions holds docker tags image options
2424
type TagsOptions struct {
25-
Image Image
26-
Max int
27-
Sort SortTag
28-
Include []string
29-
Exclude []string
25+
Image Image
26+
Max int
27+
Sort SortTag
28+
Include []string
29+
Exclude []string
30+
ExcludeOldVersions bool
3031
}
3132

3233
// Tags returns tags of a Docker repository
@@ -53,6 +54,8 @@ func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
5354
// Sort tags
5455
tags = SortTags(tags, opts.Sort)
5556

57+
foundCurrent := false
58+
5659
// Filter
5760
for _, tag := range tags {
5861
if !utl.IsIncluded(tag, opts.Include) {
@@ -61,6 +64,15 @@ func (c *Client) Tags(opts TagsOptions) (*Tags, error) {
6164
} else if utl.IsExcluded(tag, opts.Exclude) {
6265
res.Excluded++
6366
continue
67+
} else if opts.ExcludeOldVersions && opts.Sort == SortTagSemver {
68+
if foundCurrent {
69+
res.Excluded++
70+
continue
71+
}
72+
73+
if tag == opts.Image.Tag {
74+
foundCurrent = true
75+
}
6476
}
6577
res.List = append(res.List, tag)
6678
}

0 commit comments

Comments
 (0)