github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/robots/pr-creator/main.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "flag" 22 "fmt" 23 "os" 24 "strings" 25 26 "github.com/sirupsen/logrus" 27 28 "k8s.io/test-infra/prow/config/secret" 29 "k8s.io/test-infra/prow/flagutil" 30 "k8s.io/test-infra/prow/github" 31 ) 32 33 type options struct { 34 github flagutil.GitHubOptions 35 36 branch string 37 confirm bool 38 local bool 39 org string 40 repo string 41 source string 42 43 title string 44 matchTitle string 45 body string 46 } 47 48 func (o options) validate() error { 49 switch { 50 case o.org == "": 51 return errors.New("--org must be set") 52 case o.repo == "": 53 return errors.New("--repo must be set") 54 case o.branch == "": 55 return errors.New("--branch must be set") 56 case o.source == "": 57 return errors.New("--source must be set") 58 case !o.local && !strings.Contains(o.source, ":"): 59 return fmt.Errorf("--source=%s requires --local", o.source) 60 } 61 if err := o.github.Validate(!o.confirm); err != nil { 62 return err 63 } 64 return nil 65 } 66 67 func optionsFromFlags() options { 68 var o options 69 fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError) 70 o.github.AddFlags(fs) 71 fs.StringVar(&o.repo, "repo", "", "Github repo") 72 fs.StringVar(&o.org, "org", "", "Github org") 73 fs.StringVar(&o.branch, "branch", "", "Repo branch to merge into") 74 fs.StringVar(&o.source, "source", "", "The user:branch to merge from") 75 76 fs.BoolVar(&o.confirm, "confirm", false, "Set to mutate github instead of a dry run") 77 fs.BoolVar(&o.local, "local", false, "Allow source to be local-branch instead of remote-user:branch") 78 fs.StringVar(&o.title, "title", "", "Title of PR") 79 fs.StringVar(&o.matchTitle, "match-title", "", "Reuse any self-authored, open PR matching title") 80 fs.StringVar(&o.body, "body", "", "Body of PR") 81 fs.Parse(os.Args[1:]) 82 return o 83 } 84 85 func main() { 86 o := optionsFromFlags() 87 if err := o.validate(); err != nil { 88 logrus.WithError(err).Fatal("bad flags") 89 } 90 91 jamesBond := &secret.Agent{} 92 if err := jamesBond.Start([]string{o.github.TokenPath}); err != nil { 93 logrus.WithError(err).Fatal("Failed to start secrets agent") 94 } 95 96 gc, err := o.github.GitHubClient(jamesBond, !o.confirm) 97 if err != nil { 98 logrus.WithError(err).Fatal("Failed to create github client") 99 } 100 101 n, err := updatePR(o, gc) 102 if err != nil { 103 logrus.WithError(err).Fatalf("Failed to update %d", n) 104 } 105 if n == nil { 106 allowMods := true 107 pr, err := gc.CreatePullRequest(o.org, o.repo, o.title, o.body, o.source, o.branch, allowMods) 108 if err != nil { 109 logrus.WithError(err).Fatal("Failed to create PR") 110 } 111 n = &pr 112 } 113 114 logrus.Infof("PR %s/%s#%d will merge %s into %s: %s", o.org, o.repo, *n, o.source, o.branch, o.title) 115 116 fmt.Println(*n) 117 } 118 119 type updateClient interface { 120 UpdatePullRequest(org, repo string, number int, title, body *string, open *bool, branch *string, canModify *bool) error 121 BotName() (string, error) 122 FindIssues(query, sort string, asc bool) ([]github.Issue, error) 123 } 124 125 func updatePR(o options, gc updateClient) (*int, error) { 126 if o.matchTitle == "" { 127 return nil, nil 128 } 129 130 logrus.Info("Looking for a PR to reuse...") 131 me, err := gc.BotName() 132 if err != nil { 133 return nil, fmt.Errorf("bot name: %v", err) 134 } 135 136 issues, err := gc.FindIssues("is:open is:pr archived:false in:title author:"+me+" "+o.matchTitle, "updated", true) 137 if err != nil { 138 return nil, fmt.Errorf("find issues: %v", err) 139 } else if len(issues) == 0 { 140 logrus.Info("No reusable issues found") 141 return nil, nil 142 } 143 n := issues[0].Number 144 logrus.Infof("Found %d", n) 145 var ignoreOpen *bool 146 var ignoreBranch *string 147 var ignoreModify *bool 148 if err := gc.UpdatePullRequest(o.org, o.repo, n, &o.title, &o.body, ignoreOpen, ignoreBranch, ignoreModify); err != nil { 149 return nil, fmt.Errorf("update %d: %v", n, err) 150 } 151 152 return &n, nil 153 }