github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/moonraker/main.go (about)

     1  /*
     2  Copyright 2022 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  // "Moonraker" is a caching service to cache inrepoconfig (ProwYAML) objects. It
    18  // handles cloning the Git repo holding the inrepoconfig, parsing the ProwYAML
    19  // out of it, as well as caching the result in an in-memory LRU cache. Other
    20  // Prow components in the same service cluster can go through Moonraker to save
    21  // the trouble of trying to perform inrepoconfig lookups themselves.
    22  
    23  package main
    24  
    25  import (
    26  	"context"
    27  	"flag"
    28  	"fmt"
    29  	"net/http"
    30  	"os"
    31  	"strconv"
    32  	"time"
    33  
    34  	"github.com/prometheus/client_golang/prometheus"
    35  	"github.com/sirupsen/logrus"
    36  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    37  
    38  	"sigs.k8s.io/prow/pkg/config"
    39  	"sigs.k8s.io/prow/pkg/diskutil"
    40  	"sigs.k8s.io/prow/pkg/flagutil"
    41  	prowflagutil "sigs.k8s.io/prow/pkg/flagutil"
    42  	configflagutil "sigs.k8s.io/prow/pkg/flagutil/config"
    43  	"sigs.k8s.io/prow/pkg/interrupts"
    44  	"sigs.k8s.io/prow/pkg/logrusutil"
    45  	"sigs.k8s.io/prow/pkg/metrics"
    46  	"sigs.k8s.io/prow/pkg/moonraker"
    47  	"sigs.k8s.io/prow/pkg/pjutil"
    48  	"sigs.k8s.io/prow/pkg/pjutil/pprof"
    49  )
    50  
    51  var (
    52  	diskFree = prometheus.NewGauge(prometheus.GaugeOpts{
    53  		Name: "moonraker_disk_free",
    54  		Help: "Free gb on moonraker disk.",
    55  	})
    56  	diskUsed = prometheus.NewGauge(prometheus.GaugeOpts{
    57  		Name: "moonraker_disk_used",
    58  		Help: "Used gb on moonraker disk.",
    59  	})
    60  	diskTotal = prometheus.NewGauge(prometheus.GaugeOpts{
    61  		Name: "moonraker_disk_total",
    62  		Help: "Total gb on moonraker disk.",
    63  	})
    64  	diskInodeFree = prometheus.NewGauge(prometheus.GaugeOpts{
    65  		Name: "moonraker_disk_inode_free",
    66  		Help: "Free inodes on moonraker disk.",
    67  	})
    68  	diskInodeUsed = prometheus.NewGauge(prometheus.GaugeOpts{
    69  		Name: "moonraker_disk_inode_used",
    70  		Help: "Used inodes on moonraker disk.",
    71  	})
    72  	diskInodeTotal = prometheus.NewGauge(prometheus.GaugeOpts{
    73  		Name: "moonraker_disk_inode_total",
    74  		Help: "Total inodes on moonraker disk.",
    75  	})
    76  )
    77  
    78  func init() {
    79  	prometheus.MustRegister(diskFree)
    80  	prometheus.MustRegister(diskUsed)
    81  	prometheus.MustRegister(diskTotal)
    82  	prometheus.MustRegister(diskInodeFree)
    83  	prometheus.MustRegister(diskInodeUsed)
    84  	prometheus.MustRegister(diskInodeTotal)
    85  }
    86  
    87  type options struct {
    88  	github         prowflagutil.GitHubOptions
    89  	port           int
    90  	cookiefilePath string
    91  
    92  	config configflagutil.ConfigOptions
    93  
    94  	dryRun                 bool
    95  	gracePeriod            time.Duration
    96  	instrumentationOptions prowflagutil.InstrumentationOptions
    97  	pushGatewayInterval    time.Duration
    98  }
    99  
   100  func gatherOptions(fs *flag.FlagSet, args ...string) options {
   101  	var o options
   102  	fs.IntVar(&o.port, "port", 8080, "HTTP port.")
   103  	// Kubernetes uses a 30-second default grace period for pods to
   104  	// terminate before sending a SIGKILL to the process in the pod. Our own
   105  	// grace period must be smaller than this.
   106  	fs.DurationVar(&o.gracePeriod, "grace-period", 25*time.Second, "On shutdown, try to handle remaining events for the specified duration. Cannot be larger than 30s.")
   107  	fs.StringVar(&o.cookiefilePath, "cookiefile", "", "Path to git http.cookiefile, leave empty for github or anonymous")
   108  	fs.DurationVar(&o.pushGatewayInterval, "push-gateway-interval", time.Minute, "Interval at which prometheus metrics for disk space are pushed.")
   109  	for _, group := range []flagutil.OptionGroup{&o.github, &o.instrumentationOptions, &o.config} {
   110  		group.AddFlags(fs)
   111  	}
   112  
   113  	fs.Parse(args)
   114  
   115  	return o
   116  }
   117  
   118  func (o *options) validate() error {
   119  	var errs []error
   120  	for _, group := range []flagutil.OptionGroup{&o.github, &o.instrumentationOptions, &o.config} {
   121  		if err := group.Validate(o.dryRun); err != nil {
   122  			errs = append(errs, err)
   123  		}
   124  	}
   125  
   126  	return utilerrors.NewAggregate(errs)
   127  }
   128  
   129  func main() {
   130  	logrusutil.ComponentInit()
   131  
   132  	o := gatherOptions(flag.NewFlagSet(os.Args[0], flag.ExitOnError), os.Args[1:]...)
   133  	if err := o.validate(); err != nil {
   134  		logrus.WithError(err).Fatal("Invalid options")
   135  	}
   136  
   137  	pprof.Instrument(o.instrumentationOptions)
   138  
   139  	// Start serving liveness endpoint /healthz.
   140  	health := pjutil.NewHealthOnPort(o.instrumentationOptions.HealthPort)
   141  
   142  	configAgent, err := o.config.ConfigAgent()
   143  	if err != nil {
   144  		logrus.WithError(err).Fatal("Error starting config agent.")
   145  	}
   146  
   147  	metrics.ExposeMetrics("moonraker", configAgent.Config().PushGateway, o.instrumentationOptions.MetricsPort)
   148  
   149  	persist := false
   150  	if o.config.InRepoConfigCacheDirBase != "" {
   151  		persist = true
   152  	}
   153  
   154  	gitClient, err := o.github.GitClientFactory(o.cookiefilePath, &o.config.InRepoConfigCacheDirBase, o.dryRun, persist)
   155  	if err != nil {
   156  		logrus.WithError(err).Fatal("Error getting Git client.")
   157  	}
   158  
   159  	if o.config.InRepoConfigCacheDirBase != "" {
   160  		go diskMonitor(o.pushGatewayInterval, o.config.InRepoConfigCacheDirBase)
   161  	}
   162  
   163  	cacheGetter, err := config.NewInRepoConfigCache(o.config.InRepoConfigCacheSize, configAgent, gitClient)
   164  	if err != nil {
   165  		logrus.WithError(err).Fatal("Error creating InRepoConfigCacheGetter.")
   166  	}
   167  
   168  	mr := moonraker.Moonraker{
   169  		ConfigAgent:       configAgent,
   170  		InRepoConfigCache: cacheGetter,
   171  	}
   172  
   173  	// If the main config changes (an update to the ConfigMap holding the main
   174  	// config), we have to reload it because the "in_repo_config" setting which
   175  	// allowlists repositories may have changed (a repository may have been
   176  	// enabled or disabled from inrepoconfig). We have to check this setting
   177  	// before we do the clone or fetch.
   178  	logrus.Info("Setting up ConfigWatcher")
   179  	interrupts.Run(func(ctx context.Context) {
   180  		if err := mr.RunConfigWatcher(ctx); err != nil {
   181  			logrus.WithError(err).Fatal("Failed to run ConfigWatcher")
   182  		}
   183  	})
   184  
   185  	mux := http.NewServeMux()
   186  	mux.HandleFunc(fmt.Sprintf("/%s", moonraker.PathPing), mr.ServePing)
   187  	mux.HandleFunc(fmt.Sprintf("/%s", moonraker.PathGetInrepoconfig), mr.ServeGetInrepoconfig)
   188  	server := &http.Server{
   189  		Addr:    ":" + strconv.Itoa(o.port),
   190  		Handler: mux,
   191  	}
   192  	logrus.Infof("Listening on port %d...", o.port)
   193  	interrupts.ListenAndServe(server, o.gracePeriod)
   194  	health.ServeReady(func() bool {
   195  		return true
   196  	})
   197  	interrupts.WaitForGracefulShutdown()
   198  }
   199  
   200  // diskMonitor was copied from ghproxy.
   201  func diskMonitor(interval time.Duration, diskRoot string) {
   202  	logger := logrus.WithField("sync-loop", "disk-monitor")
   203  	ticker := time.NewTicker(interval)
   204  	for ; true; <-ticker.C {
   205  		logger.Info("tick")
   206  		_, bytesFree, bytesUsed, _, inodesFree, inodesUsed, err := diskutil.GetDiskUsage(diskRoot)
   207  		if err != nil {
   208  			logger.WithError(err).Error("Failed to get disk metrics")
   209  		} else {
   210  			diskFree.Set(float64(bytesFree) / 1e9)
   211  			diskUsed.Set(float64(bytesUsed) / 1e9)
   212  			diskTotal.Set(float64(bytesFree+bytesUsed) / 1e9)
   213  			diskInodeFree.Set(float64(inodesFree))
   214  			diskInodeUsed.Set(float64(inodesUsed))
   215  			diskInodeTotal.Set(float64(inodesFree + inodesUsed))
   216  		}
   217  	}
   218  }