From 8ac64ccecd1b81ab8fee4b64bc46b21913564767 Mon Sep 17 00:00:00 2001 From: Hongxiang Jiang Date: Tue, 27 Aug 2024 17:03:30 +0000 Subject: [PATCH] internal/task: reuse or create release milestone and issue If there are open release milestone and release issue in golang/vscode-go, the flow will reuse them. A local relui screenshot is at https://github.com/golang/vscode-go/issues/3500#issuecomment-2313190704 For golang/vscode-go#3500 Change-Id: I7ba69a670bd66618bdb294761901af0fb7fd0dd1 Reviewed-on: https://go-review.googlesource.com/c/build/+/608417 Reviewed-by: Hyang-Ah Hana Kim LUCI-TryBot-Result: Go LUCI --- cmd/relui/main.go | 4 ++ internal/task/fakes.go | 3 + internal/task/releasevscodego.go | 51 +++++++++++++- internal/task/releasevscodego_test.go | 68 +++++++++++++++++++ .../task/template/vscode-go-release-issue.md | 31 +++++++++ 5 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 internal/task/template/vscode-go-release-issue.md diff --git a/cmd/relui/main.go b/cmd/relui/main.go index 2ee1845b..28119248 100644 --- a/cmd/relui/main.go +++ b/cmd/relui/main.go @@ -308,6 +308,10 @@ func main() { dh.RegisterDefinition("Update x/crypto NSS root bundle", bundleTasks.NewDefinition()) releaseVSCodeGoTasks := task.ReleaseVSCodeGoTasks{ + GitHub: &task.GitHubClient{ + V3: github.NewClient(githubHTTPClient), + V4: githubv4.NewClient(githubHTTPClient), + }, Gerrit: gerritClient, ApproveAction: relui.ApproveActionDep(dbPool), } diff --git a/internal/task/fakes.go b/internal/task/fakes.go index c151e3f9..92e095ab 100644 --- a/internal/task/fakes.go +++ b/internal/task/fakes.go @@ -915,6 +915,9 @@ func (f *FakeGitHub) FetchMilestone(_ context.Context, owner, repo, name string, if create { newID := f.nextMilestoneID() + if f.Milestones == nil { + f.Milestones = map[int]string{} + } f.Milestones[newID] = name return newID, nil } diff --git a/internal/task/releasevscodego.go b/internal/task/releasevscodego.go index bedc55d6..427f353d 100644 --- a/internal/task/releasevscodego.go +++ b/internal/task/releasevscodego.go @@ -5,8 +5,10 @@ package task import ( + _ "embed" "fmt" + "github.com/google/go-github/v48/github" "golang.org/x/build/internal/relui/groups" "golang.org/x/build/internal/workflow" wf "golang.org/x/build/internal/workflow" @@ -43,6 +45,7 @@ import ( // insider versions. type ReleaseVSCodeGoTasks struct { Gerrit GerritClient + GitHub GitHubClientInterface ApproveAction func(*wf.TaskContext) error } @@ -57,18 +60,62 @@ var nextVersionParam = wf.ParamDef[string]{ }, } +//go:embed template/vscode-go-release-issue.md +var vscodeGOReleaseIssueTmplStr string + // NewPrereleaseDefinition create a new workflow definition for vscode-go pre-release. func (r *ReleaseVSCodeGoTasks) NewPrereleaseDefinition() *wf.Definition { wd := wf.New(wf.ACL{Groups: []string{groups.ToolsTeam}}) versionBumpStrategy := wf.Param(wd, nextVersionParam) - version := wf.Task1(wd, "find the next pre-release version", r.nextPrereleaseVersion, versionBumpStrategy) - _ = wf.Action1(wd, "await release coordinator's approval", r.approveVersion, version) + semv := wf.Task1(wd, "find the next pre-release version", r.nextPrereleaseVersion, versionBumpStrategy) + approved := wf.Action1(wd, "await release coordinator's approval", r.approveVersion, semv) + + _ = wf.Task1(wd, "create release milestone and issue", r.createReleaseMilestoneAndIssue, semv, wf.After(approved)) return wd } +func (r *ReleaseVSCodeGoTasks) createReleaseMilestoneAndIssue(ctx *wf.TaskContext, semv semversion) (int, error) { + version := fmt.Sprintf("v%v.%v.%v", semv.Major, semv.Minor, semv.Patch) + + // The vscode-go release milestone name matches the release version. + milestoneID, err := r.GitHub.FetchMilestone(ctx, "golang", "vscode-go", version, true) + if err != nil { + return 0, err + } + + title := fmt.Sprintf("Release %s", version) + issues, err := r.GitHub.FetchMilestoneIssues(ctx, "golang", "vscode-go", milestoneID) + if err != nil { + return 0, err + } + for id := range issues { + issue, _, err := r.GitHub.GetIssue(ctx, "golang", "vscode-go", id) + if err != nil { + return 0, err + } + if title == issue.GetTitle() { + ctx.Printf("found existing releasing issue %v", id) + return id, nil + } + } + + content := fmt.Sprintf(vscodeGOReleaseIssueTmplStr, version) + issue, _, err := r.GitHub.CreateIssue(ctx, "golang", "vscode-go", &github.IssueRequest{ + Title: &title, + Body: &content, + Assignee: github.String("h9jiang"), + Milestone: &milestoneID, + }) + if err != nil { + return 0, fmt.Errorf("failed to create release tracking issue for %q: %w", version, err) + } + ctx.Printf("created releasing issue %v", *issue.Number) + return *issue.Number, nil +} + // nextPrereleaseVersion determines the next pre-release version for the // upcoming stable release of vscode-go by examining all existing tags in the // repository. diff --git a/internal/task/releasevscodego_test.go b/internal/task/releasevscodego_test.go index 3cbed717..944b93dc 100644 --- a/internal/task/releasevscodego_test.go +++ b/internal/task/releasevscodego_test.go @@ -8,9 +8,77 @@ import ( "context" "testing" + "github.com/google/go-github/v48/github" "golang.org/x/build/internal/workflow" ) +func TestCreateReleaseMilestoneAndIssue(t *testing.T) { + testcases := []struct { + name string + version string + fakeGithub FakeGitHub + wantIssue int + wantMilestone int + }{ + { + name: "flow should create a milestone and create an issue under the milestone", + version: "v0.45.0-rc.1", + fakeGithub: FakeGitHub{}, // no issues and no milestones. + wantIssue: 1, + wantMilestone: 1, + }, + { + name: "flow should create an issue under the existing milestone", + version: "v0.48.0-rc.1", + fakeGithub: FakeGitHub{ + Milestones: map[int]string{999: "v0.48.0", 998: "v0.46.0"}, + }, + wantIssue: 1, + wantMilestone: 999, + }, + { + name: "flow should reuse the existing release issue", + version: "v0.48.0-rc.1", + fakeGithub: FakeGitHub{ + Milestones: map[int]string{999: "v0.48.0", 998: "Release v0.46.0"}, + Issues: map[int]*github.Issue{1000: {Number: github.Int(1000), Title: github.String("Release v0.48.0"), Milestone: &github.Milestone{ID: github.Int64(999)}}}, + }, + wantIssue: 1000, + wantMilestone: 999, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + tasks := &ReleaseVSCodeGoTasks{ + GitHub: &tc.fakeGithub, + } + + semv, ok := parseSemver(tc.version) + if !ok { + t.Fatalf("parseSemver(%q) should success", tc.version) + } + issueNumber, err := tasks.createReleaseMilestoneAndIssue(&workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}}, semv) + if err != nil { + t.Fatal(err) + } + + issue, ok := tc.fakeGithub.Issues[issueNumber] + if !ok { + t.Errorf("release issue with number %v does not exist", issueNumber) + } + + if *issue.Number != tc.wantIssue { + t.Errorf("createReleaseMilestoneAndIssue() create an issue with number %v, but should create issue with number %v", issue.Number, tc.wantIssue) + } + + if int(*issue.Milestone.ID) != tc.wantMilestone { + t.Errorf("release issue is created under milestone %v should under milestone %v", *issue.Milestone.ID, tc.wantMilestone) + } + }) + } +} + func TestNextPrereleaseVersion(t *testing.T) { tests := []struct { name string diff --git a/internal/task/template/vscode-go-release-issue.md b/internal/task/template/vscode-go-release-issue.md new file mode 100644 index 00000000..df3c3bad --- /dev/null +++ b/internal/task/template/vscode-go-release-issue.md @@ -0,0 +1,31 @@ +# Release candidate (TODO: DATE) +- [x] Announce the release, leave enough time for teams to surface any last minute issues that need to get in before freeze. Make sure debugger and gopls teams are looped in as well. +- [ ] Check [the milestone](https://github.com/golang/vscode-go/issues?q=milestone%%3A%[1]s) and resolve/move unresolved issues. +- [ ] Update master for the release + - [ ] Update hardcoded latest version for gopls. + - [ ] Update [CHANGELOG.md](https://github.com/golang/vscode-go/blob/master/extension/CHANGELOG.md). + - [ ] Make sure the "Thanks" section is up-to-date. + - [ ] Check the Markdown rendering to make sure everything looks good. +- [ ] Update release for the release + - [ ] Create a branch against release for a pull request. + - [ ] Merge changes from master to prepare for the release. + - [ ] Change the version in [package.json](https://github.com/golang/vscode-go/blob/master/extension/package.json) from a -dev suffix + - [ ] Run npm install to make sure [package-lock.json](https://github.com/golang/vscode-go/blob/master/extension/package.json) is up-to-date + - [ ] Update the license file ($ tools/license.sh; mv LICENSE.prod LICENSE) +- [ ] Check the [Long Tests status](https://github.com/golang/vscode-go/actions?query=workflow%%3A%%22Long+Tests%%22) is green. Otherwise, fix the tests, send cls for review, submit them, and repeat. +- [ ] Perform manual [smoke tests](https://github.com/golang/vscode-go/blob/master/docs/smoke-test.md) +- [ ] Create new RC version tag for %[1]s-rc.1 at gerrit’s vscode-go [repo management page](https://go-review.googlesource.com/admin/repos/vscode-go,tags) + - [ ] Go to the release page https://github.com/golang/vscode-go/releases and check if the new release candidate is up. If necessary, you can manually edit the comment by clicking the “Edit” button. Don’t mutate uploaded vsix. + - [ ] Ask @golang/tools-team and contributors to this release to test the release candidate + +# Final (TODO: DATE) +- [ ] Tag the new release for %[1]s +- [ ] Monitor the [cloud build status](https://pantheon.corp.google.com/cloud-build/dashboard?project=go-vscode-go) +- [ ] Update the release description with CHANGELOG contents +- [ ] Close the milestone + +# Prepare for the Next Release +- [ ] Update master post-release + - [ ] Bump the version number to the next monthly release in the master branch + - [ ] Update package.json + - [ ] Update package-lock.json