-
Notifications
You must be signed in to change notification settings - Fork 312
Add WithHead option to prcreator for App-auth PR creation #4974
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package prcreation | ||
|
|
||
| import "sigs.k8s.io/prow/pkg/github" | ||
|
|
||
| // OrgAwareClient wraps a github.Client so that FindIssues routes through | ||
| // FindIssuesWithOrg. Prow's App auth round-tripper requires the org in | ||
| // the request context to resolve the installation token, but | ||
| // bumper.UpdatePullRequestWithLabels internally calls FindIssues which | ||
| // passes an empty org. | ||
| type OrgAwareClient struct { | ||
| github.Client | ||
| Org string | ||
| } | ||
|
|
||
| func (c *OrgAwareClient) FindIssues(query, sort string, asc bool) ([]github.Issue, error) { | ||
| return c.Client.FindIssuesWithOrg(c.Org, query, sort, asc) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,8 +31,10 @@ func (o *PRCreationOptions) Finalize() error { | |||||||||||||||||||||||
| if err := o.GitHubOptions.Validate(false); err != nil { | ||||||||||||||||||||||||
| return err | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| if err := secret.Add(o.TokenPath); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("failed to start secretAgent: %w", err) | ||||||||||||||||||||||||
| if o.TokenPath != "" { | ||||||||||||||||||||||||
| if err := secret.Add(o.TokenPath); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("failed to start secretAgent: %w", err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| var err error | ||||||||||||||||||||||||
| o.GithubClient, err = o.GitHubClient(false) | ||||||||||||||||||||||||
|
|
@@ -51,6 +53,7 @@ type PrOptions struct { | |||||||||||||||||||||||
| prAssignee string | ||||||||||||||||||||||||
| gitCommitMessage string | ||||||||||||||||||||||||
| skipPRCreation bool | ||||||||||||||||||||||||
| head string | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // PrOption is the type for Optional Parameters | ||||||||||||||||||||||||
|
|
@@ -99,6 +102,14 @@ func SkipPRCreation() PrOption { | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // WithHead skips all git operations (fork, commit, push) and jumps | ||||||||||||||||||||||||
| // straight to PR creation using the provided head ref (e.g. "user:branch"). | ||||||||||||||||||||||||
| func WithHead(head string) PrOption { | ||||||||||||||||||||||||
| return func(args *PrOptions) { | ||||||||||||||||||||||||
| args.head = head | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // UpsertPR upserts a PR. The PRTitle must be alphanumeric except for spaces, as it will be used as the | ||||||||||||||||||||||||
| // branchname on the bots fork. | ||||||||||||||||||||||||
| func (o *PRCreationOptions) UpsertPR(localSourceDir, org, repo, branch, prTitle string, setters ...PrOption) error { | ||||||||||||||||||||||||
|
|
@@ -109,6 +120,11 @@ func (o *PRCreationOptions) UpsertPR(localSourceDir, org, repo, branch, prTitle | |||||||||||||||||||||||
| if prArgs.matchTitle == "" { | ||||||||||||||||||||||||
| prArgs.matchTitle = prTitle | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if prArgs.head != "" { | ||||||||||||||||||||||||
| return o.upsertPR(org, repo, branch, prTitle, prArgs) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if err := os.Chdir(localSourceDir); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("failed to chdir into %s: %w", localSourceDir, err) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+167
to
170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restore the original working directory after Both paths mutate process-global cwd and never restore it. In long-lived callers, later file/git ops can run in the wrong directory. Proposed fix func (o *PRCreationOptions) upsertWithPAT(localSourceDir, org, repo, branch, prTitle string, prArgs *PrOptions) error {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return fmt.Errorf("failed to get current working directory: %w", err)
+ }
if err := os.Chdir(localSourceDir); err != nil {
return fmt.Errorf("failed to chdir into %s: %w", localSourceDir, err)
}
+ defer func() {
+ _ = os.Chdir(cwd)
+ }()
- changed, err := bumper.HasChanges()
+ changed, err := bumper.HasChanges()
if err != nil {
return fmt.Errorf("failed to check for changes: %w", err)
} func (o *PRCreationOptions) upsertWithAppAuth(localSourceDir, org, repo, branch, prTitle string, prArgs *PrOptions) error {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return fmt.Errorf("failed to get current working directory: %w", err)
+ }
if err := os.Chdir(localSourceDir); err != nil {
return fmt.Errorf("failed to chdir into %s: %w", localSourceDir, err)
}
+ defer func() {
+ _ = os.Chdir(cwd)
+ }()
- changed, err := bumper.HasChanges()
+ changed, err := bumper.HasChanges()
if err != nil {
return fmt.Errorf("failed to check for changes: %w", err)
}Also applies to: 244-246 🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
@@ -192,35 +208,48 @@ func (o *PRCreationOptions) UpsertPR(localSourceDir, org, repo, branch, prTitle | |||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| prArgs.head = username + ":" + sourceBranchName | ||||||||||||||||||||||||
| return o.upsertPR(org, repo, branch, prTitle, prArgs) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // upsertPR creates or updates a PR for a pre-pushed head ref. | ||||||||||||||||||||||||
| func (o *PRCreationOptions) upsertPR(org, repo, branch, prTitle string, prArgs *PrOptions) error { | ||||||||||||||||||||||||
| headBranch := prArgs.head | ||||||||||||||||||||||||
| if i := strings.Index(headBranch, ":"); i != -1 { | ||||||||||||||||||||||||
| headBranch = headBranch[i+1:] | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| labelsToAdd := prArgs.additionalLabels | ||||||||||||||||||||||||
| if o.SelfApprove { | ||||||||||||||||||||||||
| l.Infof("Self-aproving PR by adding the %q and %q labels", labels.Approved, labels.LGTM) | ||||||||||||||||||||||||
| logrus.Infof("Self-aproving PR by adding the %q and %q labels", labels.Approved, labels.LGTM) | ||||||||||||||||||||||||
| labelsToAdd = append(labelsToAdd, labels.Approved, labels.LGTM) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
322
to
326
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copy
Proposed fix- labelsToAdd := prArgs.additionalLabels
+ labelsToAdd := append([]string(nil), prArgs.additionalLabels...)
if o.SelfApprove {
logrus.Infof("Self-approving PR by adding the %q and %q labels", labels.Approved, labels.LGTM)
labelsToAdd = append(labelsToAdd, labels.Approved, labels.LGTM)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| assignees := strings.Split(prArgs.prAssignee, ",") | ||||||||||||||||||||||||
| for i, assignee := range assignees { | ||||||||||||||||||||||||
| assignees[i] = "@" + assignee | ||||||||||||||||||||||||
| prBody := prArgs.prBody + formatAssigneeCC(prArgs.prAssignee) | ||||||||||||||||||||||||
| prClient := github.Client(&OrgAwareClient{Client: o.GithubClient, Org: org}) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return bumper.UpdatePullRequestWithLabels( | ||||||||||||||||||||||||
| prClient, org, repo, prTitle, prBody, | ||||||||||||||||||||||||
| prArgs.head, branch, headBranch, | ||||||||||||||||||||||||
| true, labelsToAdd, false, | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func formatAssigneeCC(assigneeCSV string) string { | ||||||||||||||||||||||||
| if assigneeCSV == "" { | ||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| assigneeList := strings.Join(assignees, ", ") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if err := bumper.UpdatePullRequestWithLabels( | ||||||||||||||||||||||||
| o.GithubClient, | ||||||||||||||||||||||||
| org, | ||||||||||||||||||||||||
| repo, | ||||||||||||||||||||||||
| prTitle, | ||||||||||||||||||||||||
| prArgs.prBody+"\n/cc "+assigneeList, | ||||||||||||||||||||||||
| username+":"+sourceBranchName, | ||||||||||||||||||||||||
| branch, | ||||||||||||||||||||||||
| sourceBranchName, | ||||||||||||||||||||||||
| true, | ||||||||||||||||||||||||
| labelsToAdd, | ||||||||||||||||||||||||
| false, | ||||||||||||||||||||||||
| ); err != nil { | ||||||||||||||||||||||||
| return fmt.Errorf("failed to upsert PR: %w", err) | ||||||||||||||||||||||||
| var mentions []string | ||||||||||||||||||||||||
| for _, a := range strings.Split(assigneeCSV, ",") { | ||||||||||||||||||||||||
| a = strings.TrimSpace(a) | ||||||||||||||||||||||||
| if a != "" { | ||||||||||||||||||||||||
| mentions = append(mentions, "@"+a) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||
| if len(mentions) == 0 { | ||||||||||||||||||||||||
| return "" | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return "\n/cc " + strings.Join(mentions, ", ") | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| func waitForRepo(owner, name string, ghc github.Client) error { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate execution mode inputs before branching.
This branch should fail fast for malformed
--headand for non-head mode without a token path; otherwise failures happen later during API/git operations and are harder to diagnose.Suggested fix
if prArgs.head != "" { + parts := strings.SplitN(prArgs.head, ":", 2) + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return fmt.Errorf("invalid --head value %q, expected <owner>:<branch>", prArgs.head) + } return o.upsertPR(org, repo, branch, prTitle, prArgs) } + + if o.TokenPath == "" { + return fmt.Errorf("token path is required when --head is not set") + }📝 Committable suggestion
🤖 Prompt for AI Agents