github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/experiment/add-hook/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  	"bytes"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"strings"
    27  
    28  	"github.com/sirupsen/logrus"
    29  
    30  	"k8s.io/test-infra/prow/config/secret"
    31  	"k8s.io/test-infra/prow/flagutil"
    32  	"k8s.io/test-infra/prow/github"
    33  )
    34  
    35  type options struct {
    36  	flagutil.GitHubOptions
    37  	repo     flagutil.Strings
    38  	hookURL  string
    39  	hmacPath string
    40  	confirm  bool
    41  	events   flagutil.Strings
    42  }
    43  
    44  func (o options) githubClient() (*github.Client, error) {
    45  	agent := &secret.Agent{}
    46  	if err := agent.Start([]string{o.TokenPath}); err != nil {
    47  		return nil, fmt.Errorf("start %s: %v", o.TokenPath, err)
    48  	}
    49  	return o.GitHubClient(agent, !o.confirm)
    50  }
    51  
    52  func getOptions(fs *flag.FlagSet, args []string) (*options, error) {
    53  	o := options{}
    54  	o.AddFlags(fs)
    55  	o.events = flagutil.NewStrings(github.AllHookEvents...)
    56  	fs.Var(&o.events, "event", "Receive hooks for the following events, defaults to [\"*\"] (all events)")
    57  	fs.Var(&o.repo, "repo", "Add hooks for this org or org/repo")
    58  	fs.StringVar(&o.hookURL, "hook-url", "", "URL to send hooks")
    59  	fs.StringVar(&o.hmacPath, "hmac-path", "", "Path to hmac secret")
    60  	fs.BoolVar(&o.confirm, "confirm", false, "Apply changes to github")
    61  	fs.Parse(args)
    62  	if o.hmacPath == "" {
    63  		return nil, errors.New("--hmac-path must be set")
    64  	}
    65  	if o.hookURL == "" {
    66  		return nil, errors.New("--hook-url must be set")
    67  	}
    68  	if len(o.repo.Strings()) == 0 {
    69  		return nil, errors.New("no --repos set")
    70  	}
    71  	if err := o.Validate(!o.confirm); err != nil {
    72  		return nil, err
    73  	}
    74  	return &o, nil
    75  }
    76  
    77  func (o options) hmac() (string, error) {
    78  	b, err := ioutil.ReadFile(o.hmacPath)
    79  	if err != nil {
    80  		return "", fmt.Errorf("read %s: %v", o.hmacPath, err)
    81  	}
    82  	return string(bytes.TrimSpace(b)), nil
    83  }
    84  
    85  func findHook(hooks []github.Hook, url string) *int {
    86  	for _, h := range hooks {
    87  		if h.Config.URL == url {
    88  			return &h.ID
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  type changer struct {
    95  	lister  func(org string) ([]github.Hook, error)
    96  	editor  func(org string, id int, req github.HookRequest) error
    97  	creator func(org string, req github.HookRequest) (int, error)
    98  }
    99  
   100  func orgChanger(client *github.Client) changer {
   101  	return changer{
   102  		lister:  client.ListOrgHooks,
   103  		editor:  client.EditOrgHook,
   104  		creator: client.CreateOrgHook,
   105  	}
   106  }
   107  
   108  func repoChanger(client *github.Client, repo string) changer {
   109  	return changer{
   110  		lister: func(org string) ([]github.Hook, error) {
   111  			return client.ListRepoHooks(org, repo)
   112  		},
   113  		editor: func(org string, id int, req github.HookRequest) error {
   114  			return client.EditRepoHook(org, repo, id, req)
   115  		},
   116  		creator: func(org string, req github.HookRequest) (int, error) {
   117  			return client.CreateRepoHook(org, repo, req)
   118  		},
   119  	}
   120  }
   121  
   122  func main() {
   123  	o, err := getOptions(flag.NewFlagSet(os.Args[0], flag.ExitOnError), os.Args[1:])
   124  	if err != nil {
   125  		logrus.Fatalf("Bad flags: %v", err)
   126  	}
   127  
   128  	client, err := o.githubClient()
   129  	if err != nil {
   130  		logrus.Fatalf("Could not create github client: %v", err)
   131  	}
   132  
   133  	hmac, err := o.hmac()
   134  	if err != nil {
   135  		logrus.Fatalf("Could not load hmac secret: %v", err)
   136  	}
   137  
   138  	yes := true
   139  	j := "json"
   140  	req := github.HookRequest{
   141  		Name:   "web",
   142  		Active: &yes,
   143  		Config: &github.HookConfig{
   144  			URL:         o.hookURL,
   145  			ContentType: &j,
   146  			Secret:      &hmac,
   147  		},
   148  		Events: o.events.Strings(),
   149  	}
   150  	for _, orgRepo := range o.repo.Strings() {
   151  		parts := strings.SplitN(orgRepo, "/", 2)
   152  		var ch changer
   153  		if len(parts) == 1 {
   154  			ch = orgChanger(client)
   155  		} else {
   156  			ch = repoChanger(client, parts[1])
   157  		}
   158  
   159  		org := parts[0]
   160  		if err := reconcileHook(ch, org, req); err != nil {
   161  			logrus.Fatalf("Could not apply hook to %s: %v", orgRepo, err)
   162  		}
   163  	}
   164  }
   165  
   166  func reconcileHook(ch changer, org string, req github.HookRequest) error {
   167  	hooks, err := ch.lister(org)
   168  	if err != nil {
   169  		return fmt.Errorf("list: %v", err)
   170  	}
   171  	id := findHook(hooks, req.Config.URL)
   172  	if id == nil {
   173  		_, err := ch.creator(org, req)
   174  		return err
   175  	}
   176  	return ch.editor(org, *id, req)
   177  }