Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

generate release notes for delta since latest release of each chart #439

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ snapshot:
dockers:
- goos: linux
goarch: amd64
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
dockerfile: Dockerfile
use: buildx
image_templates:
Expand All @@ -77,7 +77,7 @@ dockers:

- goos: linux
goarch: arm64
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
dockerfile: Dockerfile
use: buildx
image_templates:
Expand All @@ -97,7 +97,7 @@ dockers:
- goos: linux
goarch: arm
goarm: 7
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
dockerfile: Dockerfile
use: buildx
image_templates:
Expand All @@ -116,7 +116,7 @@ dockers:

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

- goos: linux
goarch: ppc64le
skip_push: "{{ if ne .GitURL \"https://github.com/helm/chart-releaser\" }}true{{ else }}false{{ end }}"
skip_push: "{{ ne .GitURL \"https://github.com/helm/chart-releaser\" }}"
dockerfile: Dockerfile
use: buildx
image_templates:
Expand Down
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