github.com/abayer/test-infra@v0.0.5/prow/cmd/tide/main.go (about)

     1  /*
     2  Copyright 2017 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  	"fmt"
    23  	"net/http"
    24  	"net/url"
    25  	"os"
    26  	"os/signal"
    27  	"strconv"
    28  	"syscall"
    29  	"time"
    30  
    31  	"github.com/sirupsen/logrus"
    32  
    33  	"k8s.io/test-infra/prow/config"
    34  	"k8s.io/test-infra/prow/flagutil"
    35  	"k8s.io/test-infra/prow/git"
    36  	"k8s.io/test-infra/prow/github"
    37  	"k8s.io/test-infra/prow/kube"
    38  	"k8s.io/test-infra/prow/logrusutil"
    39  	"k8s.io/test-infra/prow/tide"
    40  )
    41  
    42  type options struct {
    43  	port int
    44  
    45  	dryRun  bool
    46  	runOnce bool
    47  	deckURL string
    48  
    49  	configPath    string
    50  	jobConfigPath string
    51  	cluster       string
    52  
    53  	githubEndpoint  flagutil.Strings
    54  	githubTokenFile string
    55  }
    56  
    57  func gatherOptions() options {
    58  	o := options{
    59  		githubEndpoint: flagutil.NewStrings("https://api.github.com"),
    60  	}
    61  	flag.IntVar(&o.port, "port", 8888, "Port to listen on.")
    62  
    63  	flag.BoolVar(&o.dryRun, "dry-run", true, "Whether to mutate any real-world state.")
    64  	flag.BoolVar(&o.runOnce, "run-once", false, "If true, run only once then quit.")
    65  	flag.StringVar(&o.deckURL, "deck-url", "", "Deck URL for read-only access to the cluster.")
    66  
    67  	flag.StringVar(&o.configPath, "config-path", "/etc/config/config.yaml", "Path to config.yaml.")
    68  	flag.StringVar(&o.jobConfigPath, "job-config-path", "", "Path to prow job configs.")
    69  	flag.StringVar(&o.cluster, "cluster", "", "Path to kube.Cluster YAML file. If empty, uses the local cluster.")
    70  
    71  	flag.Var(&o.githubEndpoint, "github-endpoint", "GitHub's API endpoint.")
    72  	flag.StringVar(&o.githubTokenFile, "github-token-file", "/etc/github/oauth", "Path to the file containing the GitHub OAuth token.")
    73  
    74  	flag.Parse()
    75  	return o
    76  }
    77  
    78  func main() {
    79  	o := gatherOptions()
    80  
    81  	logrus.SetFormatter(
    82  		logrusutil.NewDefaultFieldsFormatter(nil, logrus.Fields{"component": "tide"}),
    83  	)
    84  
    85  	configAgent := &config.Agent{}
    86  	if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil {
    87  		logrus.WithError(err).Fatal("Error starting config agent.")
    88  	}
    89  
    90  	var err error
    91  	for _, ep := range o.githubEndpoint.Strings() {
    92  		_, err = url.ParseRequestURI(ep)
    93  		if err != nil {
    94  			logrus.WithError(err).Fatalf("Invalid --endpoint URL %q.", ep)
    95  		}
    96  	}
    97  
    98  	var tokens []string
    99  	tokens = append(tokens, o.githubTokenFile)
   100  
   101  	secretAgent := &config.SecretAgent{}
   102  	if err := secretAgent.Start(tokens); err != nil {
   103  		logrus.WithError(err).Fatal("Error starting secrets agent.")
   104  	}
   105  
   106  	var ghcSync, ghcStatus *github.Client
   107  	var kc *kube.Client
   108  	if o.dryRun {
   109  		ghcSync = github.NewDryRunClient(secretAgent.GetTokenGenerator(o.githubTokenFile), o.githubEndpoint.Strings()...)
   110  		ghcStatus = github.NewDryRunClient(secretAgent.GetTokenGenerator(o.githubTokenFile), o.githubEndpoint.Strings()...)
   111  		if o.deckURL == "" {
   112  			logrus.Fatal("no deck URL was given for read-only ProwJob access")
   113  		}
   114  		kc = kube.NewFakeClient(o.deckURL)
   115  	} else {
   116  		ghcSync = github.NewClient(secretAgent.GetTokenGenerator(o.githubTokenFile), o.githubEndpoint.Strings()...)
   117  		ghcStatus = github.NewClient(secretAgent.GetTokenGenerator(o.githubTokenFile), o.githubEndpoint.Strings()...)
   118  		if o.cluster == "" {
   119  			kc, err = kube.NewClientInCluster(configAgent.Config().ProwJobNamespace)
   120  			if err != nil {
   121  				logrus.WithError(err).Fatal("Error getting kube client.")
   122  			}
   123  		} else {
   124  			kc, err = kube.NewClientFromFile(o.cluster, configAgent.Config().ProwJobNamespace)
   125  			if err != nil {
   126  				logrus.WithError(err).Fatal("Error getting kube client.")
   127  			}
   128  		}
   129  	}
   130  	// The sync loop should be allowed more tokens than the status loop because
   131  	// it has to list all PRs in the pool every loop while the status loop only
   132  	// has to list changed PRs every loop.
   133  	// The sync loop should have a much lower burst allowance than the status
   134  	// loop which may need to update many statuses upon restarting Tide after
   135  	// changing the context format or starting Tide on a new repo.
   136  	ghcSync.Throttle(800, 20)
   137  	ghcStatus.Throttle(400, 200)
   138  
   139  	gc, err := git.NewClient()
   140  	if err != nil {
   141  		logrus.WithError(err).Fatal("Error getting git client.")
   142  	}
   143  	defer gc.Clean()
   144  	// Get the bot's name in order to set credentials for the git client.
   145  	botName, err := ghcSync.BotName()
   146  	if err != nil {
   147  		logrus.WithError(err).Fatal("Error getting bot name.")
   148  	}
   149  	gc.SetCredentials(botName, secretAgent.GetTokenGenerator(o.githubTokenFile))
   150  
   151  	c := tide.NewController(ghcSync, ghcStatus, kc, configAgent, gc, nil)
   152  	defer c.Shutdown()
   153  
   154  	server := &http.Server{Addr: ":" + strconv.Itoa(o.port), Handler: c}
   155  
   156  	start := time.Now()
   157  	sync(c)
   158  	if o.runOnce {
   159  		return
   160  	}
   161  	go func() {
   162  		sig := make(chan os.Signal, 1)
   163  		signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
   164  		for {
   165  			select {
   166  			case <-time.After(time.Until(start.Add(configAgent.Config().Tide.SyncPeriod))):
   167  				start = time.Now()
   168  				sync(c)
   169  			case <-sig:
   170  				logrus.Info("Tide is shutting down...")
   171  				// Shutdown the http server with a 10s timeout then return to execute
   172  				// defered c.Shutdown()
   173  				ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   174  				defer cancel() // frees ctx resources
   175  				server.Shutdown(ctx)
   176  				return
   177  			}
   178  		}
   179  	}()
   180  	logrus.WithError(server.ListenAndServe()).Warn("Tide HTTP server stopped.")
   181  }
   182  
   183  func sync(c *tide.Controller) {
   184  	start := time.Now()
   185  	if err := c.Sync(); err != nil {
   186  		logrus.WithError(err).Error("Error syncing.")
   187  	}
   188  	logrus.WithField("duration", fmt.Sprintf("%v", time.Since(start))).Info("Synced")
   189  }