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 }