Skip to content

Commit

Permalink
generate release notes for delta since latest release of each chart
Browse files Browse the repository at this point in the history
Signed-off-by: Kenneth Bingham <kenneth.bingham@netfoundry.io>
  • Loading branch information
qrkourier committed Sep 26, 2024
1 parent 6632d4d commit 5e44657
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 19 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ toolchain go1.22.4
require (
github.com/MakeNowJust/heredoc v1.0.0
github.com/Songmu/retry v0.1.0
github.com/blang/semver v3.5.1+incompatible
github.com/google/go-github/v56 v56.0.0
github.com/magefile/mage v1.15.0
github.com/mitchellh/go-homedir v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=
Expand Down
84 changes: 72 additions & 12 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,20 @@ import (

"github.com/Songmu/retry"
"github.com/pkg/errors"
"helm.sh/helm/v3/pkg/chart"

"github.com/blang/semver"
"github.com/google/go-github/v56/github"
"golang.org/x/oauth2"
)

type Release struct {
Name string
Description string
Assets []*Asset
Commit string
GenerateReleaseNotes bool
MakeLatest string
Name string
Description string
Assets []*Asset
Commit string
MakeLatest string
SemVer semver.Version
}

type Asset struct {
Expand Down Expand Up @@ -102,15 +104,73 @@ func (c *Client) GetRelease(_ context.Context, tag string) (*Release, error) {
return result, nil
}

// GetLatestChartRelease queries the GitHub API for the previous release of a chart
func (c *Client) GetLatestChartRelease(_ context.Context, prefix string) (*Release, error) {
// Append hyphen to prefix unless already present
prefix = strings.TrimSuffix(prefix, "-") + "-"

// Find all versions with tags matching prefix
opt := &github.ListOptions{
PerPage: 100,
}
var versions []semver.Version
for {
rels, resp, err := c.Repositories.ListReleases(context.TODO(), c.owner, c.repo, opt)
if err != nil {
return nil, err
} else if len(rels) == 0 {
return nil, errors.New("no releases found")
}
for _, rel := range rels {
if strings.HasPrefix(*rel.TagName, prefix) {
version := semver.MustParse(strings.TrimPrefix(*rel.TagName, prefix))
versions = append(versions, version)
}
}
if resp.NextPage == 0 {
break
}
opt.Page = resp.NextPage
}

// Sort versions ascending
semver.Sort(versions)

// Find highest version
latestVersion := versions[len(versions)-1]
var release *github.RepositoryRelease
if rel, _, err := c.Repositories.GetReleaseByTag(context.TODO(), c.owner, c.repo, prefix+latestVersion.String()); err == nil {
release = rel
}

result := &Release{
Name: *release.TagName,
Commit: *release.TargetCommitish,
SemVer: latestVersion,
}
return result, nil
}

// GenerateReleaseNotes generates the release notes for a release
func (c *Client) GenerateReleaseNotes(_ context.Context, latestRelease *Release, chart *chart.Chart) (string, error) {
notes, _, err := c.Repositories.GenerateReleaseNotes(context.TODO(), c.owner, c.repo, &github.GenerateNotesOptions{
TagName: chart.Metadata.Name + "-" + chart.Metadata.Version,
PreviousTagName: &latestRelease.Name,
})
if err != nil {
return "", err
}
return notes.Body, err
}

// CreateRelease creates a new release object in the GitHub API
func (c *Client) CreateRelease(_ context.Context, input *Release) error {
req := &github.RepositoryRelease{
Name: &input.Name,
Body: &input.Description,
TagName: &input.Name,
TargetCommitish: &input.Commit,
GenerateReleaseNotes: &input.GenerateReleaseNotes,
MakeLatest: &input.MakeLatest,
Name: &input.Name,
Body: &input.Description,
TagName: &input.Name,
TargetCommitish: &input.Commit,
MakeLatest: &input.MakeLatest,
}

release, _, err := c.Repositories.CreateRelease(context.TODO(), c.owner, c.repo, req)
Expand Down
56 changes: 49 additions & 7 deletions pkg/releaser/releaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"time"

"github.com/Songmu/retry"
"github.com/blang/semver"

"text/template"

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

type Git interface {
Expand Down Expand Up @@ -238,16 +241,52 @@ func (r *Releaser) computeReleaseName(chart *chart.Chart) (string, error) {
return releaseName, nil
}

func (r *Releaser) getReleaseNotes(chart *chart.Chart) string {
func (r *Releaser) getReleaseNotes(chart *chart.Chart) (string, error) {
if r.config.ReleaseNotesFile != "" {
for _, f := range chart.Files {
if f.Name == r.config.ReleaseNotesFile {
return string(f.Data)
return string(f.Data), nil
}
}
fmt.Printf("The release note file %q, is not present in the chart package\n", r.config.ReleaseNotesFile)
}
return chart.Metadata.Description
if r.config.GenerateReleaseNotes {
latestRelease, err := r.github.GetLatestChartRelease(context.TODO(), chart.Metadata.Name)
if err != nil {
return "", errors.Wrapf(err, "failed to get latest release for chart %s", chart.Metadata.Name)
}
nextVersion := semver.MustParse(chart.Metadata.Version)
versions := []semver.Version{nextVersion, latestRelease.SemVer}
semver.Sort(versions)
highest := versions[len(versions)-1]
// skip generating notes if there's already a higher version in GitHub
if nextVersion.String() == highest.String() {
notes, err := r.github.GenerateReleaseNotes(context.TODO(), latestRelease, chart)
if err != nil {
return "", errors.Wrapf(err, "failed to generate release notes for chart %s", chart.Metadata.Name)
}
return notes, nil
}
}
if r.config.GenerateReleaseNotes {
latestRelease, err := r.github.GetLatestChartRelease(context.TODO(), chart.Metadata.Name)
if err != nil {
return "", errors.Wrapf(err, "failed to get latest release for chart %s", chart.Metadata.Name)
}
nextVersion := semver.MustParse(chart.Metadata.Version)
versions := []semver.Version{nextVersion, latestRelease.SemVer}
semver.Sort(versions)
highest := versions[len(versions)-1]
// skip generating notes if there's already a higher version in GitHub
if nextVersion.String() == highest.String() {
notes, err := r.github.GenerateReleaseNotes(context.TODO(), latestRelease, chart)
if err != nil {
return "", errors.Wrapf(err, "failed to generate release notes for chart %s", chart.Metadata.Name)
}
return notes, nil
}
}
return chart.Metadata.Description, nil
}

func (r *Releaser) splitPackageNameAndVersion(pkg string) []string {
Expand Down Expand Up @@ -307,16 +346,19 @@ func (r *Releaser) CreateReleases() error {
if err != nil {
return err
}
notes, err := r.getReleaseNotes(ch)
if err != nil {
return err
}

release := &github.Release{
Name: releaseName,
Description: r.getReleaseNotes(ch),
Description: notes,
Assets: []*github.Asset{
{Path: p},
},
Commit: r.config.Commit,
GenerateReleaseNotes: r.config.GenerateReleaseNotes,
MakeLatest: strconv.FormatBool(r.config.MakeReleaseLatest),
Commit: r.config.Commit,
MakeLatest: strconv.FormatBool(r.config.MakeReleaseLatest),
}
provFile := fmt.Sprintf("%s.prov", p)
if _, err := os.Stat(provFile); err == nil {
Expand Down
27 changes: 27 additions & 0 deletions pkg/releaser/releaser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ import (
"path/filepath"
"testing"

"github.com/blang/semver"
"github.com/helm/chart-releaser/pkg/github"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"helm.sh/helm/v3/pkg/provenance"
"helm.sh/helm/v3/pkg/repo"

"github.com/helm/chart-releaser/pkg/config"
"helm.sh/helm/v3/pkg/chart"
)

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

// GetLatestChartRelease queries the GitHub API for the previous release of a chart
func (f *FakeGitHub) GetLatestChartRelease(_ context.Context, prefix string) (*github.Release, error) {
f.Called(prefix)

result := &github.Release{
Name: prefix + "-1.2.3",
Commit: "c11eea26f51782a8063ded1085384acb2928fd91",
SemVer: semver.Version{
Major: 1,
Minor: 2,
Patch: 3,
},
}
return result, nil
}

// GenerateReleaseNotes generates the release notes for a release
func (f *FakeGitHub) GenerateReleaseNotes(_ context.Context, latestRelease *github.Release, chart *chart.Chart) (string, error) {
f.Called(latestRelease, chart)

notes := "# Noted."

return notes, nil
}

func TestReleaser_UpdateIndexFile(t *testing.T) {
indexDir, _ := os.MkdirTemp(".", "index")
defer os.RemoveAll(indexDir)
Expand Down

0 comments on commit 5e44657

Please sign in to comment.