From 22864a95c82811de7ebdea741e629ac088aa8a54 Mon Sep 17 00:00:00 2001 From: Vladimir Ermakov Date: Sun, 26 Jan 2025 19:29:02 +0100 Subject: [PATCH] feat(sync): add tag excludeRegex filter (#2906) Fix #2902 Signed-off-by: Vladimir Ermakov --- examples/config-sync.json | 6 ++++ pkg/cli/server/root.go | 10 ++++++ pkg/extensions/config/sync/config.go | 5 +-- pkg/extensions/sync/content.go | 33 ++++++++++++++++++++ pkg/extensions/sync/content_internal_test.go | 28 +++++++++++++++++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/examples/config-sync.json b/examples/config-sync.json index 6c3fe8d2..2ce673df 100644 --- a/examples/config-sync.json +++ b/examples/config-sync.json @@ -41,6 +41,12 @@ }, { "prefix": "/repo3/**" + }, + { + "prefix": "/repo4/**", + "tags": { + "excludeRegex": ".*-(amd64|arm64)$" + } } ] }, diff --git a/pkg/cli/server/root.go b/pkg/cli/server/root.go index 8643d224..6eebd11f 100644 --- a/pkg/cli/server/root.go +++ b/pkg/cli/server/root.go @@ -1148,6 +1148,16 @@ func validateSync(config *config.Config, log zlog.Logger) error { } } + if content.Tags != nil && content.Tags.ExcludeRegex != nil { + _, err := regexp.Compile(*content.Tags.ExcludeRegex) + if err != nil { + msg := "sync content excludeRegex could not be compiled" + log.Error().Err(glob.ErrBadPattern).Str("excludeRegex", *content.Tags.ExcludeRegex).Msg(msg) + + return fmt.Errorf("%w: %s: %s", zerr.ErrBadConfig, msg, *content.Tags.ExcludeRegex) + } + } + if content.StripPrefix && !strings.Contains(content.Prefix, "/*") && content.Destination == "/" { msg := "can not use stripPrefix true and destination '/' without using glob patterns in prefix" log.Error().Err(zerr.ErrBadConfig). diff --git a/pkg/extensions/config/sync/config.go b/pkg/extensions/config/sync/config.go index 180420ee..54f53d13 100644 --- a/pkg/extensions/config/sync/config.go +++ b/pkg/extensions/config/sync/config.go @@ -43,6 +43,7 @@ type Content struct { } type Tags struct { - Regex *string - Semver *bool + Regex *string + ExcludeRegex *string + Semver *bool } diff --git a/pkg/extensions/sync/content.go b/pkg/extensions/sync/content.go index 3a1de48a..124dd508 100644 --- a/pkg/extensions/sync/content.go +++ b/pkg/extensions/sync/content.go @@ -63,6 +63,13 @@ func (cm ContentManager) FilterTags(repo string, tags []string) ([]string, error } } + if content.Tags.ExcludeRegex != nil { + tags, err = excludeTagsByRegex(tags, *content.Tags.ExcludeRegex, cm.log) + if err != nil { + return []string{}, err + } + } + if content.Tags.Semver != nil && *content.Tags.Semver { tags = filterTagsBySemver(tags, cm.log) } @@ -240,6 +247,32 @@ func filterTagsByRegex(tags []string, regex string, log log.Logger) ([]string, e return filteredTags, nil } +// excludeTagsByRegex filter-out images by tag regex given in the config. +func excludeTagsByRegex(tags []string, regex string, log log.Logger) ([]string, error) { + if len(tags) == 0 || regex == "" { + return tags, nil + } + + filteredTags := make([]string, 0, len(tags)) + + log.Info().Str("excludeRegex", regex).Msg("filtering out tags using regex") + + tagReg, err := regexp.Compile(regex) + if err != nil { + log.Error().Err(err).Str("excludeRegex", regex).Msg("failed to compile regex") + + return filteredTags, err + } + + for _, tag := range tags { + if !tagReg.MatchString(tag) { + filteredTags = append(filteredTags, tag) + } + } + + return filteredTags, nil +} + // filterTagsBySemver filters tags by checking if they are semver compliant. func filterTagsBySemver(tags []string, log log.Logger) []string { filteredTags := []string{} diff --git a/pkg/extensions/sync/content_internal_test.go b/pkg/extensions/sync/content_internal_test.go index 78b03b6c..eb396156 100644 --- a/pkg/extensions/sync/content_internal_test.go +++ b/pkg/extensions/sync/content_internal_test.go @@ -170,6 +170,7 @@ func TestGetContentByLocalRepo(t *testing.T) { func TestFilterTags(t *testing.T) { allTagsRegex := ".*" badRegex := "[*" + excludeArchRegex := ".*(x86_64|aarch64|amd64|arm64)$" semverFalse := false semverTrue := true testCases := []struct { @@ -234,6 +235,33 @@ func TestFilterTags(t *testing.T) { filteredTags: []string{}, err: false, }, + { + repo: "alpine", + content: []syncconf.Content{ + {Prefix: "**", Tags: &syncconf.Tags{ExcludeRegex: &allTagsRegex}}, + }, + tags: []string{"v1", "v2", "v3"}, + filteredTags: []string{}, + err: false, + }, + { + repo: "alpine", + content: []syncconf.Content{ + {Prefix: "**", Tags: &syncconf.Tags{ExcludeRegex: &excludeArchRegex}}, + }, + tags: []string{"v1", "v2-x86_64", "v3-aarch64"}, + filteredTags: []string{"v1"}, + err: false, + }, + { + repo: "repo", + content: []syncconf.Content{ + {Prefix: "repo*", Tags: &syncconf.Tags{ExcludeRegex: &badRegex}}, + }, + tags: []string{"latest", "v2.0.1"}, + filteredTags: []string{}, + err: true, + }, } Convey("Test FilterTags()", t, func() {