feat: support cooldown for Golang module#8966
Conversation
Signed-off-by: Olblak <[email protected]>
Signed-off-by: Olblak <[email protected]>
Signed-off-by: Olblak <[email protected]>
* Update spec documentation * Add test cases
Signed-off-by: Olblak <[email protected]>
Signed-off-by: Olblak <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds support for an age filter (with minimum / maximum duration constraints) to Golang module and Golang language source/condition plugins, so users can implement "cooldown" semantics that exclude releases that are too recent or too old. A new shared pkg/plugins/utils/age package introduces the spec, validation, duration parsing (with h/d/w/mo/y units), and IsOlderThan / IsNewerThan helpers. For the Golang language plugin, when age filtering is enabled, version dates are obtained by cloning the upstream Go git repository and using committer dates.
Changes:
- Introduce reusable
age.Spec(parser, validator, comparator) plus tests. - Wire age filtering into
go/modulesource and condition via Go proxy@v/<ver>.infometadata. - Wire age filtering into
go/languagesource/condition with a new git-tag-based release-date lookup (age.go).
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/plugins/utils/age/main.go | New shared age filter Spec with duration parsing and comparison helpers |
| pkg/plugins/utils/age/main_test.go | Unit tests for parser, Validate, IsOlderThan, IsNewerThan, IsZero |
| pkg/plugins/resources/go/module/spec.go | Adds Age field and documents per-mode compatibility |
| pkg/plugins/resources/go/module/main.go | Validates Age spec in constructor; includes it in ReportConfig |
| pkg/plugins/resources/go/module/version.go | Filters proxy versions/pseudo versions by release age; extracts versionInfo struct |
| pkg/plugins/resources/go/module/condition.go | Branches on Age to additionally check release date for Version |
| pkg/plugins/resources/go/module/source_test.go | Adds age-based source test cases |
| pkg/plugins/resources/go/module/condition_test.go | Adds age-based condition test cases |
| pkg/plugins/resources/go/language/spec.go | Adds Age field with documentation |
| pkg/plugins/resources/go/language/main.go | Includes Age in ReportConfig |
| pkg/plugins/resources/go/language/source.go | Selects between proxy and git-based version lookup based on Age |
| pkg/plugins/resources/go/language/condition.go | Same selection logic for condition path |
| pkg/plugins/resources/go/language/age.go | New git-clone-based tag enumeration to obtain release dates |
| pkg/plugins/resources/go/language/source_test.go | Adds age-based source tests |
| pkg/plugins/resources/go/language/condition_test.go | Adds age-based condition tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Olblak <[email protected]>
Signed-off-by: Olblak <[email protected]>
Signed-off-by: Olblak <[email protected]>
| if g.Spec.Age.Minimum != "" && g.Spec.Age.IsOlderThan(releaseDate, nil) { | ||
| logrus.Debugf("ignoring version %q from proxy %q because its age is below %q (released on %s)\n", versionToCheck, proxy, g.Spec.Age.Minimum, releaseDate) | ||
| continue | ||
| } |
| // * source | ||
| // | ||
| VersionFilter version.Filter `yaml:",omitempty"` | ||
| // age defines the minimum age of a release to be considered valid. It accepts a duration string (e.g., "24h", "7d"). |
| // IsOlderThan returns true if the version is older than the date period. | ||
| func (a Spec) IsOlderThan(t time.Time, since *time.Time) bool { | ||
| if a.Minimum == "" { | ||
| return false | ||
| } | ||
|
|
||
| if since == nil { | ||
| now := time.Now() | ||
| since = &now | ||
| } | ||
|
|
||
| duration, err := parseReleaseAge(a.Minimum) | ||
| if err != nil { | ||
| logrus.Errorf("invalid MinimumReleaseAge %q: %q\n", a.Minimum, err) | ||
| return false | ||
| } | ||
|
|
||
| return since.Sub(t) < duration | ||
| } | ||
|
|
||
| // IsNewerThan returns true if the version is newer than the date period. | ||
| func (a Spec) IsNewerThan(t time.Time, since *time.Time) bool { | ||
| if a.Maximum == "" { | ||
| return false | ||
| } | ||
|
|
||
| if since == nil { | ||
| now := time.Now() | ||
| since = &now | ||
| } | ||
|
|
||
| duration, err := parseReleaseAge(a.Maximum) | ||
| if err != nil { | ||
| logrus.Errorf("invalid MaximumReleaseAge %q: %q\n", a.Maximum, err) | ||
| return false | ||
| } | ||
|
|
||
| return since.Sub(t) > duration | ||
| } |
| // Sanitize versions by filtering out versions that are too recent based on the MinimumReleaseAge filter | ||
| if !releaseAge.IsZero() { | ||
| sanitizedVersions := []string{} | ||
|
|
||
| for v := range versions { | ||
| getVersionInfo, err := getVersionInfoFromProxy(ctx, client, proxy, module, versions[v]) | ||
| if err != nil { | ||
| logrus.Debugf("ignoring version %q from proxy %q due to %q\n", versions[v], proxy, err) | ||
| continue | ||
| } | ||
|
|
||
| releaseDate, err := time.Parse(time.RFC3339, getVersionInfo.Time) | ||
| if err != nil { | ||
| logrus.Debugf("ignoring version %q from proxy %q due to invalid release date format: %q\n", versions[v], proxy, err) | ||
| continue | ||
| } | ||
|
|
||
| if releaseAge.Minimum != "" && releaseAge.IsOlderThan(releaseDate, nil) { | ||
| logrus.Debugf("ignoring version %q from proxy %q because its age is below %q (released on %s)\n", versions[v], proxy, releaseAge.Minimum, releaseDate) | ||
| continue | ||
| } | ||
|
|
||
| if releaseAge.Maximum != "" && releaseAge.IsNewerThan(releaseDate, nil) { | ||
| logrus.Debugf("ignoring version %q from proxy %q because its age is above %q (released on %s)\n", versions[v], proxy, releaseAge.Maximum, releaseDate) | ||
| continue | ||
| } | ||
|
|
||
| sanitizedVersions = append(sanitizedVersions, versions[v]) | ||
| } | ||
| versions = sanitizedVersions | ||
| } |
| repo, err := git.PlainClone(workingDir, false, &git.CloneOptions{ | ||
| URL: GolangGitRepository, | ||
| Depth: 1, | ||
| Tags: git.AllTags, | ||
| }) | ||
|
|
||
| if err != nil { | ||
| if err != git.ErrRepositoryAlreadyExists { | ||
| return nil, err | ||
| } | ||
|
|
||
| repo, err = git.PlainOpen(workingDir) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| } | ||
|
|
||
| err = repo.Fetch(&git.FetchOptions{ | ||
| Tags: git.AllTags, | ||
| Force: true, | ||
| Depth: 1, | ||
| }) | ||
| if err != nil && err != git.NoErrAlreadyUpToDate { | ||
| return nil, err | ||
| } |
| func parseReleaseAge(releaseAge string) (time.Duration, error) { | ||
| releaseAge = strings.TrimSpace(releaseAge) | ||
|
|
||
| if strings.HasSuffix(releaseAge, "d") { | ||
| daysStr := strings.TrimSuffix(releaseAge, "d") | ||
| dayInt, err := strconv.Atoi(daysStr) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return time.ParseDuration(fmt.Sprintf("%dh", 24*dayInt)) | ||
| } | ||
|
|
||
| if strings.HasSuffix(releaseAge, "w") { | ||
| weeksStr := strings.TrimSuffix(releaseAge, "w") | ||
| weekInt, err := strconv.Atoi(weeksStr) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return time.ParseDuration(fmt.Sprintf("%dh", 24*7*weekInt)) | ||
| } | ||
| if strings.HasSuffix(releaseAge, "y") { | ||
| yearsStr := strings.TrimSuffix(releaseAge, "y") | ||
| yearInt, err := strconv.Atoi(yearsStr) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return time.ParseDuration(fmt.Sprintf("%dh", 24*365*yearInt)) | ||
| } | ||
| if strings.HasSuffix(releaseAge, "mo") { | ||
| monthsStr := strings.TrimSuffix(releaseAge, "mo") | ||
| monthInt, err := strconv.Atoi(monthsStr) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return time.ParseDuration(fmt.Sprintf("%dh", 24*365*monthInt/12)) | ||
| } | ||
|
|
||
| return time.ParseDuration(releaseAge) | ||
| } |
Related to #7866
Fix #8997
Implement the ability to filter out version newer or older than a specific date.
This pull request adds support for Golang module.
Here is an example of a source definition:
Test
To test this pull request, you can run the following commands:
Or test manually the example above.
Additional Information
Checklist
Tradeoff
Potential improvement