diff --git a/pkg/anago/anago.go b/pkg/anago/anago.go index c93bc47f3ff..f99cce1993b 100644 --- a/pkg/anago/anago.go +++ b/pkg/anago/anago.go @@ -346,7 +346,7 @@ func (r *Release) Run() error { return errors.Wrap(err, "init log file") } - logger := log.NewStepLogger(9) + logger := log.NewStepLogger(10) logger.Infof("Using krel version:\n%s", version.Get().String()) logger.WithStep().Info("Validating options") @@ -389,6 +389,11 @@ func (r *Release) Run() error { return errors.Wrap(err, "create announcement") } + logger.WithStep().Info("Updating GitHub release page") + if err := r.client.UpdateGitHubPage(); err != nil { + return errors.Wrap(err, "updating github page") + } + logger.WithStep().Info("Archiving release") if err := r.client.Archive(); err != nil { return errors.Wrap(err, "archive release") diff --git a/pkg/anago/anagofakes/fake_release_client.go b/pkg/anago/anagofakes/fake_release_client.go index fe5c80c72b7..8746b5e3e5f 100644 --- a/pkg/anago/anagofakes/fake_release_client.go +++ b/pkg/anago/anagofakes/fake_release_client.go @@ -1,19 +1,3 @@ -/* -Copyright 2020 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - // Code generated by counterfeiter. DO NOT EDIT. package anagofakes @@ -127,6 +111,16 @@ type FakeReleaseClient struct { submitReturnsOnCall map[int]struct { result1 error } + UpdateGitHubPageStub func() error + updateGitHubPageMutex sync.RWMutex + updateGitHubPageArgsForCall []struct { + } + updateGitHubPageReturns struct { + result1 error + } + updateGitHubPageReturnsOnCall map[int]struct { + result1 error + } ValidateOptionsStub func() error validateOptionsMutex sync.RWMutex validateOptionsArgsForCall []struct { @@ -703,6 +697,59 @@ func (fake *FakeReleaseClient) SubmitReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakeReleaseClient) UpdateGitHubPage() error { + fake.updateGitHubPageMutex.Lock() + ret, specificReturn := fake.updateGitHubPageReturnsOnCall[len(fake.updateGitHubPageArgsForCall)] + fake.updateGitHubPageArgsForCall = append(fake.updateGitHubPageArgsForCall, struct { + }{}) + stub := fake.UpdateGitHubPageStub + fakeReturns := fake.updateGitHubPageReturns + fake.recordInvocation("UpdateGitHubPage", []interface{}{}) + fake.updateGitHubPageMutex.Unlock() + if stub != nil { + return stub() + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeReleaseClient) UpdateGitHubPageCallCount() int { + fake.updateGitHubPageMutex.RLock() + defer fake.updateGitHubPageMutex.RUnlock() + return len(fake.updateGitHubPageArgsForCall) +} + +func (fake *FakeReleaseClient) UpdateGitHubPageCalls(stub func() error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = stub +} + +func (fake *FakeReleaseClient) UpdateGitHubPageReturns(result1 error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = nil + fake.updateGitHubPageReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeReleaseClient) UpdateGitHubPageReturnsOnCall(i int, result1 error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = nil + if fake.updateGitHubPageReturnsOnCall == nil { + fake.updateGitHubPageReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.updateGitHubPageReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeReleaseClient) ValidateOptions() error { fake.validateOptionsMutex.Lock() ret, specificReturn := fake.validateOptionsReturnsOnCall[len(fake.validateOptionsArgsForCall)] @@ -781,6 +828,8 @@ func (fake *FakeReleaseClient) Invocations() map[string][][]interface{} { defer fake.pushGitObjectsMutex.RUnlock() fake.submitMutex.RLock() defer fake.submitMutex.RUnlock() + fake.updateGitHubPageMutex.RLock() + defer fake.updateGitHubPageMutex.RUnlock() fake.validateOptionsMutex.RLock() defer fake.validateOptionsMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/pkg/anago/anagofakes/fake_release_impl.go b/pkg/anago/anagofakes/fake_release_impl.go index 464c9483cf7..14eddb6dbd6 100644 --- a/pkg/anago/anagofakes/fake_release_impl.go +++ b/pkg/anago/anagofakes/fake_release_impl.go @@ -199,6 +199,17 @@ type FakeReleaseImpl struct { toFileReturnsOnCall map[int]struct { result1 error } + UpdateGitHubPageStub func(*announce.GitHubPageOptions) error + updateGitHubPageMutex sync.RWMutex + updateGitHubPageArgsForCall []struct { + arg1 *announce.GitHubPageOptions + } + updateGitHubPageReturns struct { + result1 error + } + updateGitHubPageReturnsOnCall map[int]struct { + result1 error + } ValidateImagesStub func(string, string, string) error validateImagesMutex sync.RWMutex validateImagesArgsForCall []struct { @@ -1164,6 +1175,67 @@ func (fake *FakeReleaseImpl) ToFileReturnsOnCall(i int, result1 error) { }{result1} } +func (fake *FakeReleaseImpl) UpdateGitHubPage(arg1 *announce.GitHubPageOptions) error { + fake.updateGitHubPageMutex.Lock() + ret, specificReturn := fake.updateGitHubPageReturnsOnCall[len(fake.updateGitHubPageArgsForCall)] + fake.updateGitHubPageArgsForCall = append(fake.updateGitHubPageArgsForCall, struct { + arg1 *announce.GitHubPageOptions + }{arg1}) + stub := fake.UpdateGitHubPageStub + fakeReturns := fake.updateGitHubPageReturns + fake.recordInvocation("UpdateGitHubPage", []interface{}{arg1}) + fake.updateGitHubPageMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeReleaseImpl) UpdateGitHubPageCallCount() int { + fake.updateGitHubPageMutex.RLock() + defer fake.updateGitHubPageMutex.RUnlock() + return len(fake.updateGitHubPageArgsForCall) +} + +func (fake *FakeReleaseImpl) UpdateGitHubPageCalls(stub func(*announce.GitHubPageOptions) error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = stub +} + +func (fake *FakeReleaseImpl) UpdateGitHubPageArgsForCall(i int) *announce.GitHubPageOptions { + fake.updateGitHubPageMutex.RLock() + defer fake.updateGitHubPageMutex.RUnlock() + argsForCall := fake.updateGitHubPageArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeReleaseImpl) UpdateGitHubPageReturns(result1 error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = nil + fake.updateGitHubPageReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeReleaseImpl) UpdateGitHubPageReturnsOnCall(i int, result1 error) { + fake.updateGitHubPageMutex.Lock() + defer fake.updateGitHubPageMutex.Unlock() + fake.UpdateGitHubPageStub = nil + if fake.updateGitHubPageReturnsOnCall == nil { + fake.updateGitHubPageReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.updateGitHubPageReturnsOnCall[i] = struct { + result1 error + }{result1} +} + func (fake *FakeReleaseImpl) ValidateImages(arg1 string, arg2 string, arg3 string) error { fake.validateImagesMutex.Lock() ret, specificReturn := fake.validateImagesReturnsOnCall[len(fake.validateImagesArgsForCall)] @@ -1260,6 +1332,8 @@ func (fake *FakeReleaseImpl) Invocations() map[string][][]interface{} { defer fake.submitMutex.RUnlock() fake.toFileMutex.RLock() defer fake.toFileMutex.RUnlock() + fake.updateGitHubPageMutex.RLock() + defer fake.updateGitHubPageMutex.RUnlock() fake.validateImagesMutex.RLock() defer fake.validateImagesMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/pkg/anago/release.go b/pkg/anago/release.go index efd8f3de100..b77bfcc425c 100644 --- a/pkg/anago/release.go +++ b/pkg/anago/release.go @@ -80,6 +80,10 @@ type releaseClient interface { // GitHub release page to contain the artifacts and their checksums. CreateAnnouncement() error + // UpdateGitHubPage updates the GitHub release page to with the source code + // and release information. + UpdateGitHubPage() error + // Archive copies the release process logs to a bucket and sets private // permissions on it. Archive() error @@ -137,6 +141,7 @@ type releaseImpl interface { CreateAnnouncement( options *announce.Options, ) error + UpdateGitHubPage(options *announce.GitHubPageOptions) error PushTags(pusher *release.GitObjectPusher, tagList []string) error PushBranches(pusher *release.GitObjectPusher, branchList []string) error PushMainBranch(pusher *release.GitObjectPusher) error @@ -251,6 +256,10 @@ func (d *defaultReleaseImpl) ArchiveRelease(options *release.ArchiverOptions) er return release.NewArchiver(options).ArchiveRelease() } +func (d *defaultReleaseImpl) UpdateGitHubPage(options *announce.GitHubPageOptions) error { + return announce.UpdateGitHubPage(options) +} + func (d *defaultReleaseImpl) PushTags( pusher *release.GitObjectPusher, tagList []string, ) error { @@ -451,6 +460,52 @@ func (d *DefaultRelease) CreateAnnouncement() error { return nil } +// UpdateGitHubPage Update the GitHub release page, uploading the +// source code +func (d *DefaultRelease) UpdateGitHubPage() error { + // Array of assets to be published in the release page + assetList := []string{ + // Build the path to the kubernetes tar file: + filepath.Join( + gitRoot, // /workspace/src/k8s.io/kubernetes + fmt.Sprintf("%s-%s", release.BuildDir, d.state.versions.Prime()), // _output-v1.20.0-beta.3/ + release.GCSStagePath, // gcs-stage/ + d.state.versions.Prime(), // v1.20.0-beta.3 + release.KubernetesTar, // kubernetes.tar.gz + ) + ":Kubernetes Source Code", + } + + // URL to the changelog: + changelogURL := fmt.Sprintf( + "https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-%d.%d.md", + d.state.semverBuildVersion.Major, + d.state.semverBuildVersion.Minor, + ) + + // Build the options set for the GitHub page + ghPageOpts := &announce.GitHubPageOptions{ + AssetFiles: assetList, + Tag: d.state.versions.Prime(), + NoMock: d.options.NoMock, + UpdateIfReleaseExists: true, + Name: "Kubernetes " + d.state.versions.Prime(), + Draft: false, + Owner: git.DefaultGithubOrg, + Repo: git.DefaultGithubRepo, + // PageTemplate: , // If we use a custom template, define it here + Substitutions: map[string]string{ + "intro": "See [kubernetes-announce@](https://groups.google.com/forum/#!forum/kubernetes-announce). " + + fmt.Sprintf("Additional binary downloads are linked in the [CHANGELOG](%s).", changelogURL), + "changelog": changelogURL, + }, + } + // Update the release page (or simply output it during mock) + if err := d.impl.UpdateGitHubPage(ghPageOpts); err != nil { + return errors.Wrap(err, "updating GitHub release page") + } + return nil +} + // Archive stores the release artifact in a bucket along with // its logs for long term conservation func (d *DefaultRelease) Archive() error { diff --git a/pkg/anago/release_test.go b/pkg/anago/release_test.go index cbca48c9e3b..b2efb14bc79 100644 --- a/pkg/anago/release_test.go +++ b/pkg/anago/release_test.go @@ -385,3 +385,36 @@ func TestPushGitObjects(t *testing.T) { } } } + +func TestUpdateGitHubPage(t *testing.T) { + for _, tc := range []struct { + prepare func(*anagofakes.FakeReleaseImpl) + shouldError bool + }{ + { // success + prepare: func(*anagofakes.FakeReleaseImpl) {}, + shouldError: false, + }, + { // Pushing list of branches fails + prepare: func(mock *anagofakes.FakeReleaseImpl) { + mock.UpdateGitHubPageReturns(err) + }, + shouldError: true, + }, + } { + opts := anago.DefaultReleaseOptions() + sut := anago.NewDefaultRelease(opts) + sut.SetState( + generateTestingReleaseState(&testStateParameters{versionsTag: &testVersionTag}), + ) + mock := &anagofakes.FakeReleaseImpl{} + tc.prepare(mock) + sut.SetImpl(mock) + err := sut.UpdateGitHubPage() + if tc.shouldError { + require.NotNil(t, err) + } else { + require.Nil(t, err) + } + } +} diff --git a/pkg/announce/github_page.go b/pkg/announce/github_page.go index b6916e9642d..d6ca981d3ad 100644 --- a/pkg/announce/github_page.go +++ b/pkg/announce/github_page.go @@ -127,6 +127,42 @@ func UpdateGitHubPage(opts *GitHubPageOptions) (err error) { return errors.Wrap(err, "processing the asset file list") } + // Substitution struct for the template + subs := struct { + Substitutions map[string]string + Assets []map[string]string + }{ + Substitutions: opts.Substitutions, + Assets: releaseAssets, + } + + // Open the template file (if a custom) + templateText := ghPageBody + if opts.PageTemplate != "" { + logrus.Debugf("Using custom page template %s", opts.PageTemplate) + templateText = opts.PageTemplate + } + // Parse the template we will use to build the release page + tmpl, err := template.New("GitHubPage").Parse(templateText) + if err != nil { + return errors.Wrap(err, "parsing github page template") + } + + // Run the template to verify the output. + output := new(bytes.Buffer) + err = tmpl.Execute(output, subs) + if err != nil { + return errors.Wrap(err, "executing page template") + } + + // If we are in mock, we write it to stdout and exit. All checks + // performed to the repo are skipped as the tag may not exist yet. + if !opts.NoMock { + logrus.Info("Mock mode, outputting the release page") + _, err := os.Stdout.Write(output.Bytes()) + return errors.Wrap(err, "writing github page to stdout") + } + // Check to see that a tag exists. // non-draft release posts to github create a tag. We don't want to // create any tags on the repo this way. The tag should already exist @@ -170,41 +206,6 @@ func UpdateGitHubPage(opts *GitHubPageOptions) (err error) { // Post release data logrus.Infof("%s the %s release on github...", releaseVerb, opts.Tag) - // Substitution struct for the template - subs := struct { - Substitutions map[string]string - Assets []map[string]string - }{ - Substitutions: opts.Substitutions, - Assets: releaseAssets, - } - - // Open the template file (if a custom) - templateText := ghPageBody - if opts.PageTemplate != "" { - logrus.Debugf("Using custom page template %s", opts.PageTemplate) - templateText = opts.PageTemplate - } - // Parse the template we will use to build the release page - tmpl, err := template.New("GitHubPage").Parse(templateText) - if err != nil { - return errors.Wrap(err, "parsing github page template") - } - - // Run the template to verify the output. - output := new(bytes.Buffer) - err = tmpl.Execute(output, subs) - if err != nil { - return errors.Wrap(err, "executing page template") - } - - // If we are in mock, we write it to stdout and exit - if !opts.NoMock { - logrus.Info("Mock mod, outputting the release page") - _, err := os.Stdout.Write(output.Bytes()) - return errors.Wrap(err, "writing github page to stdout") - } - // Call GitHub to set the release page release, err := gh.UpdateReleasePage( opts.Owner, opts.Repo, releaseID,