Skip to content

Commit 04258e3

Browse files
committed
Adds pinned-major, for only replicating image tags within a certain major version
1 parent b8ed68c commit 04258e3

File tree

4 files changed

+50
-21
lines changed

4 files changed

+50
-21
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The user is expected to take care of scheduling. Some examples include running p
1717
* [optional] `mode`: see *mirroring modes* below
1818
* [optional] `compatibility`: pick a single compatibility string (for example, `alpine` or `dind`) to filter on with any of the SemVer modes. Must be an *exact* match, tag `v1.2.3-slim` uses compatibility `slim`. Useful option for images with multiple variants
1919
* [optional] `replace-tag`: if `true`, will check if the image ID for an equal tag is the same for the source and the destination. If not, will replace the tag in the destination repository
20+
* [optional] `pinned-major`: integer value, used to denote on which SemVer major to filter. Used to only duplicate tags in a single major release
2021

2122
### Mirroring modes
2223
Replicant supports 4 types of mirroring in the `mode` field:
@@ -36,6 +37,7 @@ images:
3637
mode: highest|higher|semver|all # if not specified, use top-level mode
3738
compatibility: alpine # if not specified, will only mirror plain SemVer versions
3839
replace-tag: true|false # if not specified, defaults to false
40+
pinned-major: 6 # if not specified, all major versions are acceptable (so long as they're valid in the selected mode)
3941
image-name-2:
4042
...
4143
image-name-n:

internal/config.go

+14-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
log "github.com/sirupsen/logrus"
55
"gopkg.in/yaml.v3"
66
"os"
7+
"strconv"
78
)
89

910
var allowedModes = []string{"highest", "higher", "semver", "all", ""}
@@ -19,6 +20,7 @@ type ImageConfig struct {
1920
Mode string `yaml:"mode"`
2021
Compatibility string `yaml:"compatibility"`
2122
ReplaceTag bool `yaml:"replace-tag"`
23+
PinnedMajor string `yaml:"pinned-major"` // string, to be able to distinguish between empty and major version 0.
2224
}
2325

2426
func ReadConfig(configFile string) *Config {
@@ -38,19 +40,29 @@ func ReadConfig(configFile string) *Config {
3840
}
3941

4042
func validateConfig(config *Config) {
41-
// Validate and/or set default mode
43+
// Validate and/or set default mode.
4244
if len(config.Mode) == 0 {
4345
config.Mode = "highest"
4446
} else if !stringInSlice(config.Mode, allowedModes) {
4547
log.Fatalf("default mirroring mode %s not recognized", config.Mode)
4648
}
4749

50+
// Validate config per image.
4851
for _, imageConfig := range config.Images {
49-
if len(imageConfig.Mode) == 0 {
52+
// Validate/set Mode
53+
if imageConfig.Mode == "" {
5054
imageConfig.Mode = config.Mode
5155
} else if !stringInSlice(imageConfig.Mode, allowedModes) {
5256
log.Fatalf("image specific mirroring mode %s not recognized", imageConfig.Mode)
5357
}
58+
59+
// PinnedMajor should be an integer.
60+
if imageConfig.PinnedMajor != "" {
61+
_, err := strconv.Atoi(imageConfig.PinnedMajor)
62+
if err != nil {
63+
log.Fatal(err)
64+
}
65+
}
5466
}
5567
}
5668

internal/replicant.go

+29-19
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,24 @@ import (
99
log "github.com/sirupsen/logrus"
1010
"github.com/spf13/viper"
1111
"sort"
12+
"strconv"
1213
"strings"
1314
)
1415

1516
func Run(configFile string) {
1617
config := ReadConfig(configFile)
17-
for _, image := range config.Images {
18-
switch image.Mode {
18+
for _, imageConfig := range config.Images {
19+
switch imageConfig.Mode {
1920
case "highest":
20-
mirrorHighestTag(image)
21+
mirrorHighestTag(imageConfig)
2122
case "higher":
22-
mirrorHigherTags(image)
23+
mirrorHigherTags(imageConfig)
2324
case "semver":
24-
mirrorSemVerTags(image)
25+
mirrorSemVerTags(imageConfig)
2526
case "all":
26-
mirrorAllTags(image)
27+
mirrorAllTags(imageConfig)
2728
default:
28-
log.Fatalf("image specific mirroring mode %s not valid", image.Mode)
29+
log.Fatalf("image specific mirroring mode %s not valid", imageConfig.Mode)
2930
}
3031
}
3132
}
@@ -57,7 +58,7 @@ func mirrorSemVerTags(ic *ImageConfig) {
5758
return
5859
}
5960

60-
sorted := semVerSort(tags, ic.Compatibility)
61+
sorted := semVerSort(ic, tags)
6162
if len(sorted) > 0 {
6263
for _, tag := range sorted {
6364
mirrorTag(ic, tag.Original())
@@ -74,7 +75,7 @@ func mirrorHigherTags(ic *ImageConfig) {
7475

7576
var tagsToMirror []*semver.Version
7677

77-
highestDestinationTag := findHighestTag(ic.DestinationRepository, ic.Compatibility)
78+
highestDestinationTag := findHighestTag(ic, ic.DestinationRepository)
7879
if highestDestinationTag == nil {
7980
log.Infof("no highest tag found in %s, can't determine which tags from %s to mirror", ic.DestinationRepository, ic.SourceRepository)
8081
return
@@ -85,7 +86,7 @@ func mirrorHigherTags(ic *ImageConfig) {
8586
noTagsFound(ic.SourceRepository)
8687
return
8788
}
88-
sorted := semVerSort(tags, ic.Compatibility)
89+
sorted := semVerSort(ic, tags)
8990
if len(sorted) > 0 {
9091
for _, tag := range sorted {
9192
if tag.GreaterThan(highestDestinationTag) {
@@ -106,7 +107,7 @@ func mirrorHigherTags(ic *ImageConfig) {
106107
func mirrorHighestTag(ic *ImageConfig) {
107108
log.Infof("begin mirroring highest tag from %s to %s", ic.SourceRepository, ic.DestinationRepository)
108109

109-
tag := findHighestTag(ic.SourceRepository, ic.Compatibility)
110+
tag := findHighestTag(ic, ic.SourceRepository)
110111
if tag != nil {
111112
mirrorTag(ic, tag.Original())
112113
log.Infof("done mirroring highest tag from %s to %s", ic.SourceRepository, ic.DestinationRepository)
@@ -191,12 +192,12 @@ func getAuth(registry string) remote.Option {
191192
return remote.WithAuth(getCorrectAuth(registry))
192193
}
193194

194-
func findHighestTag(repository string, compatibility string) *semver.Version {
195+
func findHighestTag(ic *ImageConfig, repository string) *semver.Version {
195196
var versions = semver.Collection{}
196197

197198
tags := listTags(repository)
198199
if len(tags) > 0 {
199-
versions = semVerSort(tags, compatibility)
200+
versions = semVerSort(ic, tags)
200201
} else {
201202
return nil
202203
}
@@ -228,7 +229,7 @@ func listTags(repository string) []string {
228229
}
229230

230231
// semVerSort sorts SemVer versions, removes non-SemVer values.
231-
func semVerSort(xs []string, compatibility string) semver.Collection {
232+
func semVerSort(ic *ImageConfig, xs []string) semver.Collection {
232233
var xv semver.Collection
233234

234235
for _, v := range xs {
@@ -247,17 +248,26 @@ func semVerSort(xs []string, compatibility string) semver.Collection {
247248
log.Debugf("%s is probably not a SemVer version, ignoring", version.String())
248249
continue
249250
}
250-
// Check if compatibility is correct
251-
if len(compatibility) > 0 && version.Prerelease() != compatibility {
252-
log.Debugf("%s is not of the correct compatibility (%s), ignoring", version.String(), compatibility)
251+
// Check if compatibility is correct, if specified.
252+
if ic.Compatibility != "" && version.Prerelease() != ic.Compatibility {
253+
log.Debugf("%s is not of the correct compatibility (%s), ignoring", version.String(), ic.Compatibility)
253254
continue
254255
}
255-
// If no compatibility is specified, ignore prerelease versions
256-
if len(compatibility) == 0 && len(version.Prerelease()) > 0 {
256+
// If no compatibility is specified, ignore prerelease versions.
257+
if ic.Compatibility == "" && len(version.Prerelease()) > 0 {
257258
log.Debugf("%s is a prerelease version, ignoring", version.String())
258259
continue
259260
}
260261

262+
// Check if we need to filter for a pinned major.
263+
if ic.PinnedMajor != "" {
264+
pmi, _ := strconv.Atoi(ic.PinnedMajor) // Already converted before, no need to check for err again.
265+
if uint64(pmi) != version.Major() {
266+
log.Debugf("%s does not have the correct major version (%d), ignoring", version.String(), pmi)
267+
continue
268+
}
269+
}
270+
261271
xv = append(xv, version)
262272
}
263273

replicant.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,8 @@ images:
2929
destination: eu.gcr.io/tammert/google/cloud-sdk
3030
compatibility: alpine
3131
mode: highest
32+
redis:
33+
source: redis
34+
destination: eu.gcr.io/tammert/redis
35+
mode: higher
36+
pinned-major: 6

0 commit comments

Comments
 (0)