in cmd/krel/cmd/release_notes.go [301:506]
func createDraftPR(repoPath, tag string) (err error) {
tagVersion, err := util.TagStringToSemver(tag)
if err != nil {
return errors.Wrapf(err, "reading tag: %s", tag)
}
// From v1.20.0 on we use the previous minor as a starting tag
// for the Release Notes draft because the branch is fast-rowarded now:
start := util.SemverToTagString(semver.Version{
Major: tagVersion.Major,
Minor: tagVersion.Minor - 1,
Patch: 0,
})
gh := github.New()
autoCreatePullRequest := true
// Verify the repository
isrepo, err := gh.RepoIsForkOf(
releaseNotesOpts.githubOrg, releaseNotesOpts.draftRepo,
git.DefaultGithubOrg, git.DefaultGithubReleaseRepo,
)
if err != nil {
return errors.Wrapf(
err, "while checking if repository is a fork of %s/%s",
git.DefaultGithubOrg, git.DefaultGithubReleaseRepo,
)
}
if !isrepo {
return fmt.Errorf(
"cannot create PR, %s/%s is not a fork of %s/%s",
releaseNotesOpts.githubOrg,
releaseNotesOpts.draftRepo,
git.DefaultGithubOrg,
git.DefaultGithubReleaseRepo,
)
}
// Generate the notes for the current version
releaseNotes, err := gatherNotesFrom(repoPath, start)
if err != nil {
return errors.Wrapf(err, "while generating the release notes for tag %s", start)
}
branchname := draftBranchPrefix + tag
// Prepare the fork of k/sig-release
sigReleaseRepo, err := github.PrepareFork(
branchname,
git.DefaultGithubOrg, git.DefaultGithubReleaseRepo,
releaseNotesOpts.githubOrg, releaseNotesOpts.draftRepo,
)
if err != nil {
return errors.Wrap(err, "preparing local fork of kubernetes/sig-release")
}
// The release path inside the repository
releasePath := filepath.Join("releases", fmt.Sprintf("release-%d.%d", tagVersion.Major, tagVersion.Minor))
// Check if the directory exists
releaseDir := filepath.Join(sigReleaseRepo.Dir(), releasePath)
if !util.Exists(releaseDir) {
return errors.Errorf("could not find release directory %s", releaseDir)
}
// If we got the --fix flag, start the fix flow
if releaseNotesOpts.fixNotes {
_, _, err = util.Ask("Press enter to start", "y:yes|n:no|y", 10)
// In interactive mode, we will ask the user before sending the PR
autoCreatePullRequest = false
// createNotesWorkDir is idempotent, we can use it to verify the tree is complete
if err := createNotesWorkDir(releaseDir); err != nil {
return errors.Wrap(err, "creating working directory")
}
// Run the release notes fix flow
err := fixReleaseNotes(filepath.Join(releaseDir, releaseNotesWorkDir), releaseNotes)
if err != nil {
return errors.Wrap(err, "while running release notes fix flow")
}
// Create the map provider to read the changes so far
rnMapProvider, err := notes.NewProviderFromInitString(filepath.Join(releaseDir, releaseNotesWorkDir, mapsMainDirectory))
if err != nil {
return errors.Wrap(err, "creating release notes draft")
}
for _, note := range releaseNotes.ByPR() {
maps, err := rnMapProvider.GetMapsForPR(note.PrNumber)
if err != nil {
return errors.Wrapf(err, "while getting maps for PR #%d", note.PrNumber)
}
for _, noteMap := range maps {
if err := note.ApplyMap(noteMap, true); err != nil {
return errors.Wrapf(err, "applying note maps to PR #%d", note.PrNumber)
}
}
}
}
// Generate the results struct
result, err := buildNotesResult(start, releaseNotes)
if err != nil {
return errors.Wrap(err, "building release notes results")
}
// generate the notes files
logrus.Debugf("Release notes draft files will be written to %s", releaseDir)
// Write the markdown draft
//nolint:gosec // TODO(gosec): G306: Expect WriteFile permissions to be
// 0600 or less
err = os.WriteFile(filepath.Join(releaseDir, releaseNotesWorkDir, draftMarkdownFile), []byte(result.markdown), 0o644)
if err != nil {
return errors.Wrapf(err, "writing release notes draft")
}
logrus.Infof("Release Notes Markdown Draft written to %s", filepath.Join(releaseDir, releaseNotesWorkDir, draftMarkdownFile))
// Write the JSON file of the current notes
//nolint:gosec // TODO(gosec): G306: Expect WriteFile permissions to be
// 0600 or less
err = os.WriteFile(filepath.Join(releaseDir, releaseNotesWorkDir, draftJSONFile), []byte(result.json), 0o644)
if err != nil {
return errors.Wrapf(err, "writing release notes json file")
}
logrus.Infof("Release Notes JSON version written to %s", filepath.Join(releaseDir, releaseNotesWorkDir, draftJSONFile))
// If we are in interactive mode, ask before continuing
if !autoCreatePullRequest {
_, autoCreatePullRequest, err = util.Ask("Create pull request with your changes? (y/n)", "y:Y:yes|n:N:no|y", 10)
if err != nil {
return errors.Wrap(err, "while asking to create pull request")
}
}
if !autoCreatePullRequest {
fmt.Println("\nPull request has NOT been created. The changes were made to your local copy of k/sig-release.")
fmt.Println("To complete the process, you will need to:")
fmt.Println(" 1. Review the changes in your local copy")
fmt.Printf(" 2. Push the changes to your fork (git push -u %s %s)\n", userForkName, branchname)
fmt.Println(" 3. Submit a pull request to k/sig-release")
fmt.Println("\nYou can find your local copy here:")
fmt.Println(sigReleaseRepo.Dir())
fmt.Println(nl)
logrus.Warn("Changes were made locally, user needs to perform manual push and create pull request.")
return nil
}
defer func() {
if e := sigReleaseRepo.Cleanup(); e != nil {
err = errors.Wrap(e, "cleaning temporary sig release clone")
}
}()
// Create the commit
if err := createDraftCommit(
sigReleaseRepo, releasePath, "Release Notes draft for k/k "+tag,
); err != nil {
return errors.Wrap(err, "creating release notes commit")
}
// push to the user's remote
logrus.Infof("Pushing modified release notes draft to %s/%s", releaseNotesOpts.githubOrg, releaseNotesOpts.draftRepo)
if err := sigReleaseRepo.PushToRemote(userForkName, branchname); err != nil {
return errors.Wrapf(err, "pushing %s to remote", userForkName)
}
// Create a PR against k/sig-release using the github API
// TODO: Maybe read and parse the PR template from sig-release?
prBody := "**What type of PR is this?**\n"
prBody += "/kind documentation\n\n"
prBody += "**What this PR does / why we need it**:\n"
prBody += fmt.Sprintf("This PR updates the Release Notes Draft to k/k %s\n\n", tag)
prBody += "**Which issue(s) this PR fixes**:\n\n"
prBody += "**Special notes for your reviewer**:\n"
prBody += "This is an automated PR generated from `krel The Kubernetes Release Toolbox`\n\n"
// Create the pull request
logrus.Debugf(
"PR params: org: %s, repo: %s, headBranch: %s baseBranch: %s",
git.DefaultGithubOrg, git.DefaultGithubReleaseRepo, git.DefaultBranch,
fmt.Sprintf("%s:%s", releaseNotesOpts.githubOrg, branchname),
)
// Create the PR
pr, err := gh.CreatePullRequest(
git.DefaultGithubOrg, git.DefaultGithubReleaseRepo, git.DefaultBranch,
fmt.Sprintf("%s:%s", releaseNotesOpts.githubOrg, branchname),
fmt.Sprintf("Update release notes draft to version %s", tag), prBody,
)
if err != nil {
logrus.Warnf("An error has occurred while creating the pull request for %s", tag)
logrus.Warn("While the PR failed, the release notes draft was generated and submitted to your fork")
return errors.Wrap(err, "creating the pull request")
}
logrus.Infof(
"Successfully created PR: %s%s/%s/pull/%d",
github.GitHubURL, git.DefaultGithubOrg,
git.DefaultGithubReleaseRepo, pr.GetNumber(),
)
logrus.Infof("Successfully created PR #%d", pr.GetNumber())
return err
}