func createDraftPR()

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
}