Skip to content

Commit 314af37

Browse files
committed
generate release notes for delta since latest release of each chart
Signed-off-by: Kenneth Bingham <kenneth.bingham@netfoundry.io>
1 parent eb1482f commit 314af37

File tree

6 files changed

+147
-24
lines changed

6 files changed

+147
-24
lines changed

.goreleaser.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ snapshot:
5858
dockers:
5959
- goos: linux
6060
goarch: amd64
61-
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
61+
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
6262
dockerfile: Dockerfile
6363
use: buildx
6464
image_templates:
@@ -77,7 +77,7 @@ dockers:
7777

7878
- goos: linux
7979
goarch: arm64
80-
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
80+
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
8181
dockerfile: Dockerfile
8282
use: buildx
8383
image_templates:
@@ -97,7 +97,7 @@ dockers:
9797
- goos: linux
9898
goarch: arm
9999
goarm: 7
100-
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
100+
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
101101
dockerfile: Dockerfile
102102
use: buildx
103103
image_templates:
@@ -116,7 +116,7 @@ dockers:
116116

117117
- goos: linux
118118
goarch: s390x
119-
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
119+
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
120120
dockerfile: Dockerfile
121121
use: buildx
122122
image_templates:
@@ -135,7 +135,7 @@ dockers:
135135

136136
- goos: linux
137137
goarch: ppc64le
138-
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
138+
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
139139
dockerfile: Dockerfile
140140
use: buildx
141141
image_templates:

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ toolchain go1.23.5
77
require (
88
github.com/MakeNowJust/heredoc v1.0.0
99
github.com/Songmu/retry v0.1.0
10+
github.com/blang/semver v3.5.1+incompatible
1011
github.com/google/go-github/v56 v56.0.0
1112
github.com/magefile/mage v1.15.0
1213
github.com/mitchellh/go-homedir v1.1.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
3838
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
3939
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
4040
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
41+
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
42+
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
4143
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
4244
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
4345
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=

pkg/github/github.go

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,23 @@ import (
2424

2525
"github.com/Songmu/retry"
2626
"github.com/pkg/errors"
27+
"helm.sh/helm/v3/pkg/chart"
2728

29+
"github.com/blang/semver"
2830
"github.com/google/go-github/v56/github"
2931
"golang.org/x/oauth2"
3032
)
3133

34+
// Define a custom error for no releases found
35+
var ErrNoReleasesFound = errors.New("no releases found")
36+
3237
type Release struct {
33-
Name string
34-
Description string
35-
Assets []*Asset
36-
Commit string
37-
GenerateReleaseNotes bool
38-
MakeLatest string
38+
Name string
39+
Description string
40+
Assets []*Asset
41+
Commit string
42+
MakeLatest string
43+
SemVer semver.Version
3944
}
4045

4146
type Asset struct {
@@ -102,15 +107,75 @@ func (c *Client) GetRelease(_ context.Context, tag string) (*Release, error) {
102107
return result, nil
103108
}
104109

110+
// GetLatestChartRelease queries the GitHub API for the previous release of a chart
111+
func (c *Client) GetLatestChartRelease(_ context.Context, prefix string) (*Release, error) {
112+
// Append hyphen to prefix unless already present
113+
prefix = strings.TrimSuffix(prefix, "-") + "-"
114+
115+
// Find all versions with tags matching prefix
116+
opt := &github.ListOptions{
117+
PerPage: 100,
118+
}
119+
var versions []semver.Version
120+
for {
121+
rels, resp, err := c.Repositories.ListReleases(context.TODO(), c.owner, c.repo, opt)
122+
if err != nil {
123+
return nil, err
124+
}
125+
for _, rel := range rels {
126+
if strings.HasPrefix(*rel.TagName, prefix) {
127+
version := semver.MustParse(strings.TrimPrefix(*rel.TagName, prefix))
128+
versions = append(versions, version)
129+
}
130+
}
131+
if resp.NextPage == 0 {
132+
break
133+
}
134+
opt.Page = resp.NextPage
135+
}
136+
137+
if len(versions) == 0 {
138+
return nil, ErrNoReleasesFound
139+
}
140+
141+
// Sort versions ascending
142+
semver.Sort(versions)
143+
144+
// Find highest version
145+
latestVersion := versions[len(versions)-1]
146+
var release *github.RepositoryRelease
147+
if rel, _, err := c.Repositories.GetReleaseByTag(context.TODO(), c.owner, c.repo, prefix+latestVersion.String()); err == nil {
148+
release = rel
149+
}
150+
151+
result := &Release{
152+
Name: *release.TagName,
153+
Commit: *release.TargetCommitish,
154+
SemVer: latestVersion,
155+
}
156+
return result, nil
157+
}
158+
159+
// GenerateReleaseNotes generates the release notes for a release
160+
func (c *Client) GenerateReleaseNotes(_ context.Context, latestRelease *Release, chart *chart.Chart) (string, error) {
161+
notes, _, err := c.Repositories.GenerateReleaseNotes(context.TODO(), c.owner, c.repo, &github.GenerateNotesOptions{
162+
TagName: chart.Metadata.Name + "-" + chart.Metadata.Version,
163+
PreviousTagName: &latestRelease.Name,
164+
})
165+
if err != nil {
166+
return "", err
167+
}
168+
return notes.Body, err
169+
}
170+
105171
// CreateRelease creates a new release object in the GitHub API
106172
func (c *Client) CreateRelease(_ context.Context, input *Release) error {
107173
req := &github.RepositoryRelease{
108-
Name: &input.Name,
109-
Body: &input.Description,
110-
TagName: &input.Name,
111-
TargetCommitish: &input.Commit,
112-
GenerateReleaseNotes: &input.GenerateReleaseNotes,
113-
MakeLatest: &input.MakeLatest,
174+
Name: &input.Name,
175+
Body: &input.Description,
176+
TagName: &input.Name,
177+
TargetCommitish: &input.Commit,
178+
MakeLatest: &input.MakeLatest,
114179
}
115180

116181
release, _, err := c.Repositories.CreateRelease(context.TODO(), c.owner, c.repo, req)

pkg/releaser/releaser.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"time"
2929

3030
"github.com/Songmu/retry"
31+
"github.com/blang/semver"
3132

3233
"text/template"
3334

@@ -50,6 +51,8 @@ type GitHub interface {
5051
CreateRelease(ctx context.Context, input *github.Release) error
5152
GetRelease(ctx context.Context, tag string) (*github.Release, error)
5253
CreatePullRequest(owner string, repo string, message string, head string, base string) (string, error)
54+
GetLatestChartRelease(ctx context.Context, prefix string) (*github.Release, error)
55+
GenerateReleaseNotes(ctx context.Context, latestRelease *github.Release, chart *chart.Chart) (string, error)
5356
}
5457

5558
type Git interface {
@@ -238,16 +241,38 @@ func (r *Releaser) computeReleaseName(chart *chart.Chart) (string, error) {
238241
return releaseName, nil
239242
}
240243

241-
func (r *Releaser) getReleaseNotes(chart *chart.Chart) string {
244+
func (r *Releaser) getReleaseNotes(chart *chart.Chart) (string, error) {
242245
if r.config.ReleaseNotesFile != "" {
243246
for _, f := range chart.Files {
244247
if f.Name == r.config.ReleaseNotesFile {
245-
return string(f.Data)
248+
return string(f.Data), nil
246249
}
247250
}
248251
fmt.Printf("The release note file %q, is not present in the chart package\n", r.config.ReleaseNotesFile)
249252
}
250-
return chart.Metadata.Description
253+
if r.config.GenerateReleaseNotes {
254+
latestRelease, err := r.github.GetLatestChartRelease(context.TODO(), chart.Metadata.Name)
255+
if err != nil {
256+
if errors.Is(err, github.ErrNoReleasesFound) {
257+
// Handle the case where there are no releases found
258+
return chart.Metadata.Description, nil
259+
}
260+
return "", errors.Wrapf(err, "failed to get latest release for chart %s", chart.Metadata.Name)
261+
}
262+
nextVersion := semver.MustParse(chart.Metadata.Version)
263+
versions := []semver.Version{nextVersion, latestRelease.SemVer}
264+
semver.Sort(versions)
265+
highest := versions[len(versions)-1]
266+
// skip generating notes if there's already a higher version in GitHub
267+
if nextVersion.String() == highest.String() {
268+
notes, err := r.github.GenerateReleaseNotes(context.TODO(), latestRelease, chart)
269+
if err != nil {
270+
return "", errors.Wrapf(err, "failed to generate release notes for chart %s", chart.Metadata.Name)
271+
}
272+
return notes, nil
273+
}
274+
}
275+
return chart.Metadata.Description, nil
251276
}
252277

253278
func (r *Releaser) splitPackageNameAndVersion(pkg string) []string {
@@ -317,16 +342,19 @@ func (r *Releaser) CreateReleases() error {
317342
if err != nil {
318343
return err
319344
}
345+
notes, err := r.getReleaseNotes(ch)
346+
if err != nil {
347+
return err
348+
}
320349

321350
release := &github.Release{
322351
Name: releaseName,
323-
Description: r.getReleaseNotes(ch),
352+
Description: notes,
324353
Assets: []*github.Asset{
325354
{Path: p},
326355
},
327-
Commit: r.config.Commit,
328-
GenerateReleaseNotes: r.config.GenerateReleaseNotes,
329-
MakeLatest: strconv.FormatBool(r.config.MakeReleaseLatest),
356+
Commit: r.config.Commit,
357+
MakeLatest: strconv.FormatBool(r.config.MakeReleaseLatest),
330358
}
331359
provFile := fmt.Sprintf("%s.prov", p)
332360
if _, err := os.Stat(provFile); err == nil {

pkg/releaser/releaser_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ import (
2121
"path/filepath"
2222
"testing"
2323

24+
"github.com/blang/semver"
2425
"github.com/helm/chart-releaser/pkg/github"
2526
"github.com/stretchr/testify/assert"
2627
"github.com/stretchr/testify/mock"
2728
"helm.sh/helm/v3/pkg/provenance"
2829
"helm.sh/helm/v3/pkg/repo"
2930

3031
"github.com/helm/chart-releaser/pkg/config"
32+
"helm.sh/helm/v3/pkg/chart"
3133
)
3234

3335
type FakeGitHub struct {
@@ -115,6 +117,31 @@ func (f *FakeGitHub) CreatePullRequest(owner string, repo string, message string
115117
return "https://github.com/owner/repo/pull/42", nil
116118
}
117119

120+
// GetLatestChartRelease queries the GitHub API for the previous release of a chart
121+
func (f *FakeGitHub) GetLatestChartRelease(_ context.Context, prefix string) (*github.Release, error) {
122+
f.Called(prefix)
123+
124+
result := &github.Release{
125+
Name: prefix + "-1.2.3",
126+
Commit: "c11eea26f51782a8063ded1085384acb2928fd91",
127+
SemVer: semver.Version{
128+
Major: 1,
129+
Minor: 2,
130+
Patch: 3,
131+
},
132+
}
133+
return result, nil
134+
}
135+
136+
// GenerateReleaseNotes generates the release notes for a release
137+
func (f *FakeGitHub) GenerateReleaseNotes(_ context.Context, latestRelease *github.Release, chart *chart.Chart) (string, error) {
138+
f.Called(latestRelease, chart)
139+
140+
notes := "# Noted."
141+
142+
return notes, nil
143+
}
144+
118145
func TestReleaser_UpdateIndexFile(t *testing.T) {
119146
indexDir, _ := os.MkdirTemp(".", "index")
120147
defer os.RemoveAll(indexDir)

0 commit comments

Comments
 (0)