// Copyright 2021 The Prometheus Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "fmt" "net/http" "os" "os/signal" "syscall" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/log" "github.com/prometheus/common/version" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( configFile = kingpin.Flag( "config.file", "Path to configuration file.", ).String() executablesPath = kingpin.Flag( "freeipmi.path", "Path to FreeIPMI executables (default: rely on $PATH).", ).String() listenAddress = kingpin.Flag( "web.listen-address", "Address to listen on for web interface and telemetry.", ).Default(":9290").String() sc = &SafeConfig{ C: &Config{}, } reloadCh chan chan error ) func remoteIPMIHandler(w http.ResponseWriter, r *http.Request) { target := r.URL.Query().Get("target") if target == "" { http.Error(w, "'target' parameter must be specified", 400) return } // Remote scrape will not work without some kind of config, so be pedantic about it module := r.URL.Query().Get("module") if module == "" { module = "default" } if !sc.HasModule(module) { http.Error(w, fmt.Sprintf("Unknown module %q", module), http.StatusBadRequest) return } log.Debugf("Scraping target '%s' with module '%s'", target, module) registry := prometheus.NewRegistry() remoteCollector := metaCollector{target: target, module: module, config: sc} registry.MustRegister(remoteCollector) h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{}) h.ServeHTTP(w, r) } func updateConfiguration(w http.ResponseWriter, r *http.Request) { switch r.Method { case "POST": rc := make(chan error) reloadCh <- rc if err := <-rc; err != nil { http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError) } default: log.Errorf("Only POST requests allowed for %s", r.URL) w.Header().Set("Allow", "POST") http.Error(w, "Only POST requests allowed", http.StatusMethodNotAllowed) } } func main() { log.AddFlags(kingpin.CommandLine) kingpin.HelpFlag.Short('h') kingpin.Version(version.Print("ipmi_exporter")) kingpin.Parse() log.Infoln("Starting ipmi_exporter") // Bail early if the config is bad. if err := sc.ReloadConfig(*configFile); err != nil { log.Fatalf("Error parsing config file: %s", err) } hup := make(chan os.Signal) reloadCh = make(chan chan error) signal.Notify(hup, syscall.SIGHUP) go func() { for { select { case <-hup: if err := sc.ReloadConfig(*configFile); err != nil { log.Errorf("Error reloading config: %s", err) } case rc := <-reloadCh: if err := sc.ReloadConfig(*configFile); err != nil { log.Errorf("Error reloading config: %s", err) rc <- err } else { rc <- nil } } } }() localCollector := metaCollector{target: targetLocal, module: "default", config: sc} prometheus.MustRegister(&localCollector) http.Handle("/metrics", promhttp.Handler()) // Regular metrics endpoint for local IPMI metrics. http.HandleFunc("/ipmi", remoteIPMIHandler) // Endpoint to do IPMI scrapes. http.HandleFunc("/-/reload", updateConfiguration) // Endpoint to reload configuration. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` IPMI Exporter

IPMI Exporter


Local metrics

Config

`)) }) log.Infof("Listening on %s", *listenAddress) err := http.ListenAndServe(*listenAddress, nil) if err != nil { log.Fatal(err) } }