From b17f979c23c35e4791fd3d0a23d29398eb8d9571 Mon Sep 17 00:00:00 2001 From: Vaughn Dice Date: Mon, 31 Oct 2016 13:23:43 -0600 Subject: [PATCH] ref(*): fix changelog global command To compensate for individual components now following their own release cadences --- actions/generate_changelog.go | 62 +++++++++++++++++++++++-------- actions/generate_changelog_err.go | 13 +++++++ changelog/changelog.go | 24 +++++++++++- changelog/changelog_test.go | 8 ++-- changelog/single_repo_err.go | 2 +- main.go | 2 +- 6 files changed, 90 insertions(+), 21 deletions(-) create mode 100644 actions/generate_changelog_err.go diff --git a/actions/generate_changelog.go b/actions/generate_changelog.go index fc01970..7f4beb6 100644 --- a/actions/generate_changelog.go +++ b/actions/generate_changelog.go @@ -5,9 +5,12 @@ import ( "io" "io/ioutil" "log" + "sort" "sync" + "github.com/BurntSushi/toml" "github.com/deis/deisrel/changelog" + "github.com/deis/deisrel/components" "github.com/google/go-github/github" "github.com/urfave/cli" ) @@ -15,38 +18,51 @@ import ( // GenerateChangelog is the CLI action for creating an aggregated changelog from all of the Deis Workflow repos. func GenerateChangelog(client *github.Client, dest io.Writer) func(*cli.Context) error { return func(c *cli.Context) error { - repoMapFile := c.Args().Get(0) - oldTag := c.Args().Get(1) - newTag := c.Args().Get(2) - if repoMapFile == "" || oldTag == "" || newTag == "" { - log.Fatal("Usage: changelog global ") + paramsFile := c.Args().Get(0) + repoMapFile := c.Args().Get(1) + if paramsFile == "" || repoMapFile == "" { + log.Fatal("Usage: changelog global ") } + versions := []components.ComponentVersion{} - out, err := ioutil.ReadFile(repoMapFile) + res := make(map[string]interface{}) + out, err := ioutil.ReadFile(paramsFile) if err != nil { - log.Fatal(err.Error()) + return cli.NewExitError(err.Error(), 2) + } + if err := toml.Unmarshal(out, &res); err != nil { + return cli.NewExitError(err.Error(), 3) + } + + mapping := make(map[string][]string) + out, err = ioutil.ReadFile(repoMapFile) + if err != nil { + return cli.NewExitError(err.Error(), 2) + } + if err := json.Unmarshal(out, &mapping); err != nil { + return cli.NewExitError(err.Error(), 3) } - repoMap := make(map[string][]string) - err = json.Unmarshal(out, &repoMap) + versions, err = components.CheckVersions(res, mapping, client) if err != nil { - log.Fatal(err.Error()) + return cli.NewExitError(err.Error(), 4) } - vals, errs := generateChangelogVals(client, repoMap, oldTag, newTag) + vals, errs := generateChangelogVals(client, mapping, versions) if len(errs) > 0 { for _, err := range errs { log.Printf("Error: %s", err) } } - if err := changelog.Tpl.Execute(dest, changelog.MergeValues(oldTag, newTag, vals)); err != nil { + sort.Sort(changelog.ByName(vals)) + if err := changelog.Tpl.Execute(dest, changelog.MergeValues("", "", vals)); err != nil { log.Fatalf("could not template changelog: %s", err) } return nil } } -func generateChangelogVals(client *github.Client, repoMap map[string][]string, oldTag, newTag string) ([]changelog.Values, []error) { +func generateChangelogVals(client *github.Client, repoMap map[string][]string, versions []components.ComponentVersion) ([]changelog.Values, []error) { var wg sync.WaitGroup done := make(chan bool) valsCh := make(chan changelog.Values) @@ -56,12 +72,17 @@ func generateChangelogVals(client *github.Client, repoMap map[string][]string, o wg.Add(1) go func(repo string) { defer wg.Done() - vals := &changelog.Values{OldRelease: oldTag, NewRelease: newTag} - _, err := changelog.SingleRepoVals(client, vals, newTag, repo, true) + component := repoMap[repo][0] + componentVersion, err := findComponentVersionByName(versions, component) if err != nil { errCh <- err return } + vals := &changelog.Values{RepoName: repo, OldRelease: componentVersion.ChartVersion, NewRelease: componentVersion.ComponentVersion} + if _, err := changelog.SingleRepoVals(client, vals, componentVersion.ComponentVersion, repo, true); err != nil { + errCh <- err + return + } valsCh <- *vals }(repo) } @@ -84,3 +105,14 @@ func generateChangelogVals(client *github.Client, repoMap map[string][]string, o } } } + +// findComponentVersionByName finds a particular ComponentVersion from an array of +// ComponentVersions based on Name; returns errComponentVersionNotFound if not found +func findComponentVersionByName(componentVersions []components.ComponentVersion, componentName string) (components.ComponentVersion, error) { + for _, componentVersion := range componentVersions { + if componentVersion.Name == componentName { + return componentVersion, nil + } + } + return components.ComponentVersion{}, errComponentVersionNotFound{componentName: componentName} +} diff --git a/actions/generate_changelog_err.go b/actions/generate_changelog_err.go new file mode 100644 index 0000000..3f79b7b --- /dev/null +++ b/actions/generate_changelog_err.go @@ -0,0 +1,13 @@ +package actions + +import ( + "fmt" +) + +type errComponentVersionNotFound struct { + componentName string +} + +func (e errComponentVersionNotFound) Error() string { + return fmt.Sprintf("A ComponentVersion for component named '%s' not found.", e.componentName) +} diff --git a/changelog/changelog.go b/changelog/changelog.go index 69d0307..f29bf71 100644 --- a/changelog/changelog.go +++ b/changelog/changelog.go @@ -1,12 +1,22 @@ package changelog import ( + "fmt" "text/template" ) const ( - tplStr = `### {{.OldRelease}} -> {{.NewRelease}} + tplStr = `{{ if and .OldRelease .NewRelease }} +### {{.OldRelease}} -> {{.NewRelease}} +{{end}} + +{{ if (len .Releases) gt 0 -}} +#### Releases + +{{range .Releases}}- {{.}} +{{end}} +{{- end}} {{ if (len .Features) gt 0 -}} #### Features @@ -55,8 +65,16 @@ var ( Tpl = template.Must(template.New("changelog").Parse(tplStr)) ) +// ByName takes an array of Values structs and sorts in alphabetical order of name +type ByName []Values + +func (v ByName) Len() int { return len(v) } +func (v ByName) Swap(i, j int) { v[i], v[j] = v[j], v[i] } +func (v ByName) Less(i, j int) bool { return v[i].RepoName < v[j].RepoName } + // Values represents the values that are required to render a changelog type Values struct { + RepoName string OldRelease string NewRelease string Features []string @@ -65,6 +83,7 @@ type Values struct { Tests []string Maintenance []string Refactors []string + Releases []string } // MergeValues merges all of the slices in vals together into a single Values struct which has OldRelease set to oldRel and NewRelease set to newRel @@ -77,6 +96,9 @@ func MergeValues(oldRel, newRel string, vals []Values) *Values { ret.Documentation = append(ret.Documentation, val.Documentation...) ret.Tests = append(ret.Tests, val.Tests...) ret.Maintenance = append(ret.Maintenance, val.Maintenance...) + if (val.OldRelease != val.NewRelease) { + ret.Releases = append(ret.Releases, fmt.Sprintf("%s %s -> %s", val.RepoName, val.OldRelease, val.NewRelease)) + } } return ret } diff --git a/changelog/changelog_test.go b/changelog/changelog_test.go index 1c1f742..4393d9c 100644 --- a/changelog/changelog_test.go +++ b/changelog/changelog_test.go @@ -136,13 +136,15 @@ func TestTemplate(t *testing.T) { } func TestMergeValues(t *testing.T) { - val1 := Values{Features: []string{"feat1"}} - val2 := Values{Fixes: []string{"fix1"}, Features: []string{"feat2"}} - res := MergeValues("old", "new", []Values{val1, val2}) + val1 := Values{RepoName: "repo-a", OldRelease: "v1.2.3", NewRelease: "v1.2.4", Features: []string{"feat1"}} + val2 := Values{RepoName: "repo-b", OldRelease: "v4.5.6", NewRelease: "v4.6.0", Fixes: []string{"fix1"}, Features: []string{"feat2"}} + val3 := Values{OldRelease: "v1.2.3", NewRelease: "v1.2.3"} // no change; should not be added to res.Releases + res := MergeValues("old", "new", []Values{val1, val2, val3}) assert.Equal(t, res.OldRelease, "old", "old release") assert.Equal(t, res.NewRelease, "new", "new release") assert.Equal(t, len(res.Features), 2, "length of features slice") assert.Equal(t, len(res.Fixes), 1, "length of fixes slice") assert.Equal(t, res.Features, []string{"feat1", "feat2"}, "features slice") assert.Equal(t, res.Fixes, []string{"fix1"}, "fixes slice") + assert.Equal(t, res.Releases, []string{"repo-a v1.2.3 -> v1.2.4", "repo-b v4.5.6 -> v4.6.0"}, "releases slice") } diff --git a/changelog/single_repo_err.go b/changelog/single_repo_err.go index 6fc4abd..86d9014 100644 --- a/changelog/single_repo_err.go +++ b/changelog/single_repo_err.go @@ -10,7 +10,7 @@ type errTagNotFoundForRepo struct { } func (e errTagNotFoundForRepo) Error() string { - return fmt.Sprintf("tag %s not found for repo %s", e.tagName, e.repoName) + return fmt.Sprintf("tag %s not found for repo %s; Changelog entries will need to be added in manually", e.tagName, e.repoName) } type errCouldNotCompareCommits struct { diff --git a/main.go b/main.go index 0210127..407ed03 100644 --- a/main.go +++ b/main.go @@ -66,7 +66,7 @@ func main() { cli.Command{ Name: "global", Action: actions.GenerateChangelog(ghclient, os.Stdout), - Usage: "deisrel changelog global ", + Usage: "deisrel changelog global ", Description: "Aggregate changelog entries from all known repositories for a specified release", }, cli.Command{