Skip to content

Commit

Permalink
Merge pull request #1 from netfoundry/generate-release-notes-since-la…
Browse files Browse the repository at this point in the history
…test-chart

generate release notes for delta since latest release of each chart
  • Loading branch information
qrkourier authored Jul 25, 2024
2 parents 61a6b61 + 6fd1fc4 commit 9c5207a
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 26 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ jobs:
- name: Login to registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: quay.io
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
registry: ${{ vars.OCI_REGISTRY || 'quay.io' }}
username: ${{ secrets.DOCKER_USERNAME || secrets.DOCKER_HUB_API_USER }}
password: ${{ secrets.DOCKER_PASSWORD || secrets.DOCKER_HUB_API_TOKEN }}

- name: Run Mage
uses: magefile/mage-action@6a5dcb5fe61f43d7c08a98bc3cf9bc63c308c08e # v3.0.0
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ git-upload-url: https://uploads.github.com/
cr upload --config config.yaml
```

`cr` supports any format [Viper](https://github.com/spf13/viper) can read, i. e. JSON, TOML, YAML, HCL, and Java properties files.
`cr` supports any format [Viper](https://github.com/spf13/viper) can read, i.e. JSON, TOML, YAML, HCL, and Java properties files.

Notice that if no config file is specified, `cr.yaml` (or any of the supported formats) is loaded from the current directory, `$HOME/.cr`, or `/etc/cr`, in that order, if found.

Expand All @@ -279,7 +279,7 @@ and then look for `upload_url`. You need the part of the URL that appears before

## Common Error Messages

During the upload, you can get the follwing error :
During the upload, you can get the following error :

```bash
422 Validation Failed [{Resource:Release Field:tag_name Code:already_exists Message:}]
Expand All @@ -289,8 +289,8 @@ You can solve it by adding the `--skip-existing` flag to your command. More deta

## Known Bug

Currently, if you set the upload URL incorrectly, let's say to something like `https://example.com/uploads/`, then `cr upload` will appear to work, but the release will not be complete. When everything is working there should be 3 assets in each release, but instead there will only be the 2 source code assets. The third asset, which is what helm actually uses, is missing. This issue will become apparent when you run `cr index` and it always claims that nothing has changed, because it can't find the asset it expects for the release.
Currently, if you set the upload URL incorrectly, let's say to something like `https://example.com/uploads/`, then `cr upload` will appear to work, but the release will not be complete. When everything is working there should be three assets in each release, but instead, there will only be two source code assets. The third asset is missing and is needed by Helm. This issue will become apparent when you run `cr index` and it always claims that nothing has changed, because it can't find the asset it expects for the release.

It appears like the [go-github Do call](https://github.com/google/go-github/blob/master/github/github.go#L520) does not catch the fact that the upload URL is incorrect and pass back the expected error. If the asset upload fails, it would be better if the release was rolled back (deleted) and an appropriate log message is be displayed to the user.
It appears like the [go-github Do call](https://github.com/google/go-github/blob/master/github/github.go#L520) does not catch the fact that the upload URL is incorrect and passes back the expected error. If the asset upload fails, it would be better if the release was rolled back (deleted) and an appropriate log message is displayed to the user.

The `cr index` command should also generate a warning when a release has no assets attached to it, to help people detect and troubleshoot this type of problem.
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 @@ -37,6 +37,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
83 changes: 71 additions & 12 deletions pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,18 @@ import (
"github.com/Songmu/retry"
"github.com/pkg/errors"

"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 +103,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, nextRelease string) (string, error) {
notes, _, err := c.Repositories.GenerateReleaseNotes(context.TODO(), c.owner, c.repo, &github.GenerateNotesOptions{
TagName: nextRelease,
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
37 changes: 30 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, nextRelease string) (string, error)
}

type Git interface {
Expand Down Expand Up @@ -238,16 +241,33 @@ 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)
} else 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.Metadata.Version)
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
return chart.Metadata.Description, nil
}

func (r *Releaser) splitPackageNameAndVersion(pkg string) []string {
Expand Down Expand Up @@ -307,16 +327,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
26 changes: 26 additions & 0 deletions pkg/releaser/releaser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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"
Expand Down Expand Up @@ -115,6 +116,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, nextRelease string) (string, error) {
f.Called(latestRelease, nextRelease)

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 9c5207a

Please sign in to comment.