github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/hook/main.go (about)

     1  /*
     2  Copyright 2016 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  	"context"
    21  	"flag"
    22  	"net/http"
    23  	"os"
    24  	"os/signal"
    25  	"strconv"
    26  	"syscall"
    27  	"time"
    28  
    29  	"github.com/prometheus/client_golang/prometheus/promhttp"
    30  	"github.com/sirupsen/logrus"
    31  
    32  	"k8s.io/test-infra/pkg/flagutil"
    33  	"k8s.io/test-infra/prow/config"
    34  	"k8s.io/test-infra/prow/config/secret"
    35  	prowflagutil "k8s.io/test-infra/prow/flagutil"
    36  	"k8s.io/test-infra/prow/hook"
    37  	"k8s.io/test-infra/prow/logrusutil"
    38  	"k8s.io/test-infra/prow/metrics"
    39  	pluginhelp "k8s.io/test-infra/prow/pluginhelp/hook"
    40  	"k8s.io/test-infra/prow/plugins"
    41  	"k8s.io/test-infra/prow/slack"
    42  )
    43  
    44  type options struct {
    45  	port int
    46  
    47  	configPath    string
    48  	jobConfigPath string
    49  	pluginConfig  string
    50  
    51  	dryRun      bool
    52  	gracePeriod time.Duration
    53  	kubernetes  prowflagutil.KubernetesOptions
    54  	github      prowflagutil.GitHubOptions
    55  
    56  	webhookSecretFile string
    57  	slackTokenFile    string
    58  }
    59  
    60  func (o *options) Validate() error {
    61  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} {
    62  		if err := group.Validate(o.dryRun); err != nil {
    63  			return err
    64  		}
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func gatherOptions() options {
    71  	o := options{}
    72  	fs := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
    73  	fs.IntVar(&o.port, "port", 8888, "Port to listen on.")
    74  
    75  	fs.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.")
    76  	fs.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.")
    77  	fs.StringVar(&o.pluginConfig, "plugin-config", "/etc/plugins/plugins.yaml", "Path to plugin config file.")
    78  
    79  	fs.BoolVar(&o.dryRun, "dry-run", true, "Dry run for testing. Uses API tokens but does not mutate.")
    80  	fs.DurationVar(&o.gracePeriod, "grace-period", 180*time.Second, "On shutdown, try to handle remaining events for the specified duration. ")
    81  	for _, group := range []flagutil.OptionGroup{&o.kubernetes, &o.github} {
    82  		group.AddFlags(fs)
    83  	}
    84  
    85  	fs.StringVar(&o.webhookSecretFile, "hmac-secret-file", "/etc/webhook/hmac", "Path to the file containing the GitHub HMAC secret.")
    86  	fs.StringVar(&o.slackTokenFile, "slack-token-file", "", "Path to the file containing the Slack token to use.")
    87  	fs.Parse(os.Args[1:])
    88  	return o
    89  }
    90  
    91  func main() {
    92  	o := gatherOptions()
    93  	if err := o.Validate(); err != nil {
    94  		logrus.Fatalf("Invalid options: %v", err)
    95  	}
    96  	logrus.SetFormatter(logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "hook"}))
    97  
    98  	configAgent := &config.Agent{}
    99  	if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil {
   100  		logrus.WithError(err).Fatal("Error starting config agent.")
   101  	}
   102  
   103  	var tokens []string
   104  
   105  	// Append the path of hmac and github secrets.
   106  	tokens = append(tokens, o.github.TokenPath)
   107  	tokens = append(tokens, o.webhookSecretFile)
   108  
   109  	// This is necessary since slack token is optional.
   110  	if o.slackTokenFile != "" {
   111  		tokens = append(tokens, o.slackTokenFile)
   112  	}
   113  
   114  	secretAgent := &secret.Agent{}
   115  	if err := secretAgent.Start(tokens); err != nil {
   116  		logrus.WithError(err).Fatal("Error starting secrets agent.")
   117  	}
   118  
   119  	githubClient, err := o.github.GitHubClient(secretAgent, o.dryRun)
   120  	if err != nil {
   121  		logrus.WithError(err).Fatal("Error getting GitHub client.")
   122  	}
   123  	gitClient, err := o.github.GitClient(secretAgent, o.dryRun)
   124  	if err != nil {
   125  		logrus.WithError(err).Fatal("Error getting Git client.")
   126  	}
   127  	defer gitClient.Clean()
   128  
   129  	kubeClient, err := o.kubernetes.Client(configAgent.Config().ProwJobNamespace, o.dryRun)
   130  	if err != nil {
   131  		logrus.WithError(err).Fatal("Error getting Kubernetes client.")
   132  	}
   133  
   134  	var slackClient *slack.Client
   135  	if !o.dryRun && string(secretAgent.GetSecret(o.slackTokenFile)) != "" {
   136  		logrus.Info("Using real slack client.")
   137  		slackClient = slack.NewClient(secretAgent.GetTokenGenerator(o.slackTokenFile))
   138  	}
   139  	if slackClient == nil {
   140  		logrus.Info("Using fake slack client.")
   141  		slackClient = slack.NewFakeClient()
   142  	}
   143  
   144  	clientAgent := &plugins.ClientAgent{
   145  		GitHubClient: githubClient,
   146  		KubeClient:   kubeClient,
   147  		GitClient:    gitClient,
   148  		SlackClient:  slackClient,
   149  	}
   150  
   151  	pluginAgent := &plugins.ConfigAgent{}
   152  	if err := pluginAgent.Start(o.pluginConfig); err != nil {
   153  		logrus.WithError(err).Fatal("Error starting plugins.")
   154  	}
   155  
   156  	promMetrics := hook.NewMetrics()
   157  
   158  	// Push metrics to the configured prometheus pushgateway endpoint.
   159  	pushGateway := configAgent.Config().PushGateway
   160  	if pushGateway.Endpoint != "" {
   161  		go metrics.PushMetrics("hook", pushGateway.Endpoint, pushGateway.Interval)
   162  	}
   163  
   164  	server := &hook.Server{
   165  		ClientAgent:    clientAgent,
   166  		ConfigAgent:    configAgent,
   167  		Plugins:        pluginAgent,
   168  		Metrics:        promMetrics,
   169  		TokenGenerator: secretAgent.GetTokenGenerator(o.webhookSecretFile),
   170  	}
   171  	defer server.GracefulShutdown()
   172  
   173  	// Return 200 on / for health checks.
   174  	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {})
   175  	http.Handle("/metrics", promhttp.Handler())
   176  	// For /hook, handle a webhook normally.
   177  	http.Handle("/hook", server)
   178  	// Serve plugin help information from /plugin-help.
   179  	http.Handle("/plugin-help", pluginhelp.NewHelpAgent(pluginAgent, githubClient))
   180  
   181  	httpServer := &http.Server{Addr: ":" + strconv.Itoa(o.port)}
   182  
   183  	// Shutdown gracefully on SIGTERM or SIGINT
   184  	sig := make(chan os.Signal, 1)
   185  	signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
   186  	go func() {
   187  		<-sig
   188  		logrus.Info("Hook is shutting down...")
   189  		ctx, cancel := context.WithTimeout(context.Background(), o.gracePeriod)
   190  		defer cancel()
   191  		httpServer.Shutdown(ctx)
   192  	}()
   193  
   194  	logrus.WithError(httpServer.ListenAndServe()).Warn("Server exited.")
   195  }