Skip to content

Commit 0cf2bd5

Browse files
committed
Implement more flexible release filtering rules
This commit adds three new options: - KeepReleases: how many releases should be returned starting from the latest - OnlyLatestMinor: if true returns only the latest minor.patch per major - OnlyLatestPatch: if true returns only the latest patch for each minor. OnlyLatestMinor has precedence over this Signed-off-by: Matias Pan <matipan@hey.com>
1 parent c4a65ee commit 0cf2bd5

4 files changed

Lines changed: 234 additions & 10 deletions

File tree

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,24 @@ This project is composed of 4 components:
1313

1414
To install it in your cluster you can run:
1515
```terminal
16-
$ ARGOCD_TOKEN="$(echo -n '<strong_password>' | base64)" envsubst < https://raw.githubusercontent.com/matipan/argocd-github-release-generator/v0.0.2/k8s/install.yaml | k apply -f -
16+
$ ARGOCD_TOKEN="$(echo -n '<strong_password>' | base64)" envsubst < https://raw.githubusercontent.com/matipan/argocd-github-release-generator/v0.0.3/k8s/install.yaml | k apply -f -
1717
```
1818

1919
> [!TIP]
2020
> If you plan to watch private repositories or have a refresh interval lower than 1 per minute then you must specify a GITHUB_PAT.
21-
> `$ GITHUB_PAT=<YOUR_PAT> ARGOCD_TOKEN="$(echo -n '<strong_password>' | base64)" envsubst < https://raw.githubusercontent.com/matipan/argocd-github-release-generator/v0.0.2/k8s/install.yaml | k apply -f -`
21+
> `$ GITHUB_PAT=<YOUR_PAT> ARGOCD_TOKEN="$(echo -n '<strong_password>' | base64)" envsubst < https://raw.githubusercontent.com/matipan/argocd-github-release-generator/v0.0.3/k8s/install.yaml | k apply -f -`
2222
2323
## Setting up your ApplicationSet
2424

2525
The plugin receives two parameters that you must configure:
2626
- `repository`: specify the repository that should be used to look for releases.
2727
- `min_release`: specify the starting point for the releases. This value is useful to control how many applications you generate and remove applications related to old releases.
2828

29+
You can optionally configure the following three parameters to further control which releases should be returned:
30+
- `keep_releases`: specify how many releases should be kept. If you set this value to `3` then the plugin will only return the 3 most recent releases.
31+
- `only_latest_minor`: if set to `true` then the plugin will only return the latest release for each major version.
32+
- `only_latest_patch`: if set to `true` then the plugin will only return the latest release for each minor version. This parameter is ignored if `only_latest_minor` is set to `true`.
33+
2934
> [!NOTE]
3035
> At the moment this project only supports releases that follow `semver` (e.g `v0.1.0`)
3136

k8s/install.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ spec:
5353
secretKeyRef:
5454
key: plugin.argocd-github-release-generator.github_pat
5555
name: argocd-github-release-generator
56-
image: ghcr.io/matipan/argocd-github-release-generator:v0.0.2
56+
image: ghcr.io/matipan/argocd-github-release-generator:v0.0.3
5757
imagePullPolicy: IfNotPresent
5858
name: argocd-github-release-generator
5959
ports:

main.go

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net/http"
88
"os"
9+
"sort"
910
"strings"
1011

1112
"github.com/rs/zerolog"
@@ -31,7 +32,15 @@ type Input struct {
3132

3233
type Parameters struct {
3334
Repository string `json:"repository"`
35+
// MinRelease is the minimum release that should be returned. If a release is less than this, it will be filtered out
3436
MinRelease string `json:"min_release"`
37+
// KeepReleases is the number of releases to keep. If set to 0, all releases will be returned
38+
KeepReleases int `json:"keep_releases"`
39+
// OnlyLatestMinor is a flag that if set to true, will only return the latest minor version of a release
40+
OnlyLatestMinor bool `json:"only_latest_minor"`
41+
// OnlyLatestPatch is a flag that if set to true, will only return the latest patch version of a release
42+
// if OnlyLatestMinor is set to true, this flag will be ignored
43+
OnlyLatestPatch bool `json:"only_latest_patch"`
3544
}
3645

3746
type Output struct {
@@ -104,11 +113,18 @@ func generatorHandler(l zerolog.Logger) http.HandlerFunc {
104113

105114
l.Debug().Msgf("fetched %d releases", len(releases))
106115

116+
filtered, err := getFilteredReleases(releases, req.Input.Parameters)
117+
if err != nil {
118+
l.Error().Err(err).Msg("failed to filter releases")
119+
http.Error(w, err.Error(), http.StatusInternalServerError)
120+
return
121+
}
122+
107123
out := Output{
108124
Output: struct {
109125
Parameters []Release `json:"parameters"`
110126
}{
111-
Parameters: getFilteredReleases(releases, req.Input.Parameters.MinRelease),
127+
Parameters: filtered,
112128
},
113129
}
114130

@@ -136,16 +152,60 @@ type Commit struct {
136152
URL string `json:"url"`
137153
}
138154

139-
func getFilteredReleases(releases []Release, minRelease string) []Release {
140-
var filteredReleases []Release
155+
func getFilteredReleases(releases []Release, params Parameters) ([]Release, error) {
156+
// Sort releases in a descending order so that returning only the latest patch
157+
// of a minor or latest minor of a major version can be done simply with a map
158+
sort.SliceStable(releases, func(i, j int) bool {
159+
return semver.Compare(releases[i].Name, releases[j].Name) > 0
160+
})
161+
162+
var (
163+
filteredReleases []Release
164+
latestVersion = map[string]string{}
165+
)
141166
for _, r := range releases {
142-
if semver.Compare(r.Name, minRelease) > 0 {
143-
r.NameSlug = strings.ReplaceAll(r.Name, ".", "-")
144-
filteredReleases = append(filteredReleases, r)
167+
if semver.Compare(r.Name, params.MinRelease) < 0 {
168+
continue
169+
}
170+
171+
// if we reached the amount of releases we want to keep, break out of the loop
172+
if params.KeepReleases != 0 && len(filteredReleases) == params.KeepReleases {
173+
break
145174
}
175+
176+
version := semver.MajorMinor(r.Name)
177+
major := semver.Major(r.Name)
178+
179+
if params.OnlyLatestMinor {
180+
if _, ok := latestVersion[major]; !ok {
181+
latestVersion[major] = r.Name
182+
r.NameSlug = strings.ReplaceAll(r.Name, ".", "-")
183+
filteredReleases = append(filteredReleases, r)
184+
continue
185+
}
186+
continue
187+
}
188+
189+
if params.OnlyLatestPatch {
190+
if _, ok := latestVersion[version]; !ok {
191+
latestVersion[version] = r.Name
192+
r.NameSlug = strings.ReplaceAll(r.Name, ".", "-")
193+
filteredReleases = append(filteredReleases, r)
194+
continue
195+
}
196+
continue
197+
}
198+
199+
r.NameSlug = strings.ReplaceAll(r.Name, ".", "-")
200+
filteredReleases = append(filteredReleases, r)
146201
}
147202

148-
return filteredReleases
203+
// sort the releases by increasing order before returning them
204+
sort.SliceStable(filteredReleases, func(i, j int) bool {
205+
return semver.Compare(filteredReleases[i].Name, filteredReleases[j].Name) < 0
206+
})
207+
208+
return filteredReleases, nil
149209
}
150210

151211
func getReleases(ctx context.Context, repo string) ([]Release, error) {

main_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package main
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestGetFilteredReleases(t *testing.T) {
9+
cases := []struct {
10+
name string
11+
params Parameters
12+
releases []Release
13+
expectedReleases []Release
14+
}{
15+
{
16+
name: "no filter",
17+
params: Parameters{
18+
MinRelease: "v0.0.0",
19+
},
20+
releases: []Release{
21+
{Name: "v0.0.0"},
22+
{Name: "v0.0.1"},
23+
{Name: "v0.1.0"},
24+
{Name: "v1.0.0"},
25+
},
26+
expectedReleases: []Release{
27+
{Name: "v0.0.0", NameSlug: "v0-0-0"},
28+
{Name: "v0.0.1", NameSlug: "v0-0-1"},
29+
{Name: "v0.1.0", NameSlug: "v0-1-0"},
30+
{Name: "v1.0.0", NameSlug: "v1-0-0"},
31+
},
32+
},
33+
{
34+
name: "only latest minor",
35+
params: Parameters{
36+
MinRelease: "v0.0.0",
37+
OnlyLatestMinor: true,
38+
},
39+
releases: []Release{
40+
{Name: "v0.0.0"},
41+
{Name: "v0.0.1"},
42+
{Name: "v0.1.0"},
43+
{Name: "v0.1.1"},
44+
{Name: "v1.0.0"},
45+
{Name: "v1.0.1"},
46+
},
47+
expectedReleases: []Release{
48+
{Name: "v0.1.1", NameSlug: "v0-1-1"},
49+
{Name: "v1.0.1", NameSlug: "v1-0-1"},
50+
},
51+
},
52+
{
53+
name: "only latest patch",
54+
params: Parameters{
55+
MinRelease: "v0.0.0",
56+
OnlyLatestPatch: true,
57+
},
58+
releases: []Release{
59+
{Name: "v0.0.0"},
60+
{Name: "v0.0.1"},
61+
{Name: "v0.1.0"},
62+
{Name: "v0.1.1"},
63+
},
64+
expectedReleases: []Release{
65+
{Name: "v0.0.1", NameSlug: "v0-0-1"},
66+
{Name: "v0.1.1", NameSlug: "v0-1-1"},
67+
},
68+
},
69+
{
70+
name: "only latest minor has precedence",
71+
params: Parameters{
72+
MinRelease: "v0.0.0",
73+
OnlyLatestMinor: true,
74+
OnlyLatestPatch: true,
75+
},
76+
releases: []Release{
77+
{Name: "v0.0.0"},
78+
{Name: "v0.0.1"},
79+
{Name: "v0.1.0"},
80+
{Name: "v0.1.1"},
81+
{Name: "v1.0.0"},
82+
{Name: "v1.0.1"},
83+
},
84+
expectedReleases: []Release{
85+
{Name: "v0.1.1", NameSlug: "v0-1-1"},
86+
{Name: "v1.0.1", NameSlug: "v1-0-1"},
87+
},
88+
},
89+
{
90+
name: "only latest minor combined with min release",
91+
params: Parameters{
92+
MinRelease: "v0.1.2",
93+
OnlyLatestMinor: true,
94+
},
95+
releases: []Release{
96+
{Name: "v0.0.0"},
97+
{Name: "v0.0.1"},
98+
{Name: "v0.1.0"},
99+
{Name: "v0.1.1"},
100+
{Name: "v1.0.0"},
101+
{Name: "v1.0.1"},
102+
},
103+
expectedReleases: []Release{
104+
{Name: "v1.0.1", NameSlug: "v1-0-1"},
105+
},
106+
},
107+
{
108+
name: "keep releases returns only the amount of releases we want to keep",
109+
params: Parameters{
110+
MinRelease: "v0.1.2",
111+
KeepReleases: 2,
112+
},
113+
releases: []Release{
114+
{Name: "v0.0.0"},
115+
{Name: "v0.0.1"},
116+
{Name: "v0.1.0"},
117+
{Name: "v0.1.1"},
118+
{Name: "v1.0.0"},
119+
{Name: "v1.0.1"},
120+
},
121+
expectedReleases: []Release{
122+
{Name: "v1.0.0", NameSlug: "v1-0-0"},
123+
{Name: "v1.0.1", NameSlug: "v1-0-1"},
124+
},
125+
},
126+
{
127+
name: "keep releases plus only latest minor returns right releases",
128+
params: Parameters{
129+
MinRelease: "v0.1.0",
130+
KeepReleases: 1,
131+
OnlyLatestMinor: true,
132+
},
133+
releases: []Release{
134+
{Name: "v0.0.0"},
135+
{Name: "v0.0.1"},
136+
{Name: "v0.1.0"},
137+
{Name: "v0.1.1"},
138+
{Name: "v1.0.0"},
139+
{Name: "v1.0.1"},
140+
},
141+
expectedReleases: []Release{
142+
{Name: "v1.0.1", NameSlug: "v1-0-1"},
143+
},
144+
},
145+
}
146+
147+
for _, test := range cases {
148+
t.Run(test.name, func(t *testing.T) {
149+
releases, err := getFilteredReleases(test.releases, test.params)
150+
if err != nil {
151+
t.Errorf("unexpected error: %v", err)
152+
}
153+
154+
if !reflect.DeepEqual(releases, test.expectedReleases) {
155+
t.Errorf("expected releases to be %v, got %v", test.expectedReleases, releases)
156+
}
157+
})
158+
}
159+
}

0 commit comments

Comments
 (0)