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  }