main.go
Rama main · réplica de lectura del repositorio original.
package main
import (
"bytes"
"embed"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"path"
"syscall"
"text/template"
"github.com/coroot/coroot/api"
"github.com/coroot/coroot/cache"
"github.com/coroot/coroot/cloud"
cloud_pricing "github.com/coroot/coroot/cloud-pricing"
"github.com/coroot/coroot/collector"
"github.com/coroot/coroot/config"
"github.com/coroot/coroot/db"
"github.com/coroot/coroot/grpc"
"github.com/coroot/coroot/rbac"
"github.com/coroot/coroot/stats"
"github.com/coroot/coroot/timeseries"
"github.com/coroot/coroot/utils"
"github.com/coroot/coroot/watchers"
"github.com/gorilla/mux"
"golang.org/x/term"
"gopkg.in/alecthomas/kingpin.v2"
"k8s.io/klog"
)
const Edition = "Community"
var version = "unknown"
//go:embed static
var static embed.FS
func main() {
kingpin.Command("run", "Run Coroot server").Default()
cmdSetAdminPassword := kingpin.Command("set-admin-password", "Set password for the default Admin user")
cmd := kingpin.Parse()
klog.Infof("edition: %s", Edition)
klog.Infof("version: %s", version)
utils.SetMemoryLimit()
cfg, err := config.Load()
if err != nil {
klog.Exitf("failed to load config: %s", err)
}
if err = utils.CreateDirectoryIfNotExists(cfg.DataDir); err != nil {
klog.Exitln(err)
}
var database *db.DB
if cfg.Postgres != nil && cfg.Postgres.ConnectionString != "" {
klog.Infoln("database type: postgres")
database, err = db.NewPostgres(cfg.Postgres.ConnectionString)
} else {
klog.Infoln("database type: sqlite")
database, err = db.NewSqlite(cfg.DataDir, false)
}
if err != nil {
klog.Exitln(err)
}
if err = database.Migrate(); err != nil {
klog.Exitln(err)
}
switch cmd {
case cmdSetAdminPassword.FullCommand():
err = setAdminPassword(database)
if err != nil {
fmt.Println("Failed to set admin password:", err)
} else {
fmt.Println("Admin password set successfully.")
}
return
}
err = cfg.Bootstrap(database)
if err != nil {
klog.Exitln(err)
}
instanceUuid := utils.GetInstanceUuid(cfg.DataDir)
deploymentUuid, err := database.GetDeploymentUuid()
if err != nil {
klog.Exitln(err)
}
globalClickhouse := cfg.GetGlobalClickhouse()
globalPrometheus := cfg.GetGlobalPrometheus()
cacheConfig := cache.Config{
Path: path.Join(cfg.DataDir, "cache"),
GC: &cache.GcConfig{
TTL: cfg.Cache.TTL,
Interval: cfg.Cache.GCInterval,
},
}
promCache, err := cache.NewCache(cacheConfig, database, globalPrometheus, globalClickhouse)
if err != nil {
klog.Exitln(err)
}
grpcServer, err := grpc.NewServer(cfg.GRPC, cfg.TLS)
if err != nil {
klog.Exitln("grpc server:", err)
}
collConfig := config.CollectorConfig{
TracesTTL: cfg.Traces.TTL,
LogsTTL: cfg.Logs.TTL,
ProfilesTTL: cfg.Profiles.TTL,
MetricsTTL: cfg.Metrics.TTL,
}
coll := collector.New(collConfig, database, promCache, globalClickhouse, globalPrometheus, grpcServer)
go func() {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
grpcServer.Stop()
coll.Close()
os.Exit(0)
}()
pricing, err := cloud_pricing.NewManager(path.Join(cfg.DataDir, "cloud-pricing"))
if err != nil {
klog.Exitln(err)
}
statsCollector := stats.NewCollector(cfg.DisableUsageStatistics, instanceUuid, version, Edition, database, promCache, pricing, globalClickhouse)
a := api.NewApi(cfg, promCache, database, coll, statsCollector, pricing, rbac.NewStaticRoleManager(), nil, globalClickhouse, globalPrometheus, deploymentUuid, instanceUuid, nil)
err = a.AuthInit(cfg.Auth.AnonymousRole, cfg.Auth.BootstrapAdminPassword)
if err != nil {
klog.Exitln(err)
}
incidents := watchers.NewIncidents(database, a.IncidentRCA)
watchers.Start(database, promCache, pricing, incidents, !cfg.DoNotCheckForDeployments, globalClickhouse, globalPrometheus, cfg.ClickHouseSpaceManager, nil, nil)
router := mux.NewRouter()
router.Use(statsCollector.MiddleWare)
router.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {}).Methods(http.MethodGet)
router.HandleFunc("/v1/metrics", coll.Metrics)
router.HandleFunc("/v1/traces", coll.Traces)
router.HandleFunc("/v1/logs", coll.Logs)
router.HandleFunc("/v1/profiles", coll.Profiles)
router.HandleFunc("/v1/config", coll.Config)
r := router
if cfg.UrlBasePath != "/" {
r = router.PathPrefix(cfg.UrlBasePath).Subrouter()
r.HandleFunc("/v1/metrics", coll.Metrics)
r.HandleFunc("/v1/traces", coll.Traces)
r.HandleFunc("/v1/logs", coll.Logs)
r.HandleFunc("/v1/profiles", coll.Profiles)
r.HandleFunc("/v1/config", coll.Config)
}
r.UseEncodedPath()
r.HandleFunc("/api/login", a.Login).Methods(http.MethodPost)
r.HandleFunc("/api/logout", a.Logout).Methods(http.MethodPost)
r.HandleFunc("/api/user", a.Auth(a.User)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/users", a.Auth(a.Users)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/users/{user}/api_keys", a.Auth(a.UserApiKeys)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/roles", a.Auth(a.Roles)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/sso", a.Auth(a.SSO)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/ai", a.Auth(a.AI)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/cloud", a.Auth(a.Cloud)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/", a.Auth(a.Project)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}", a.Auth(a.Project)).Methods(http.MethodGet, http.MethodPost, http.MethodDelete)
r.HandleFunc("/api/project/{project}/status", a.Auth(a.Status)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/api_keys", a.Auth(a.ApiKeys)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/overview/{view}", a.Auth(a.Overview)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/incidents", a.Auth(a.Incidents)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/incident/{incident}", a.Auth(a.Incident)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/alerts", a.Auth(a.Alerts)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/alerts/resolve", a.Auth(a.ResolveAlerts)).Methods(http.MethodPost)
r.HandleFunc("/api/project/{project}/alerts/suppress", a.Auth(a.SuppressAlerts)).Methods(http.MethodPost)
r.HandleFunc("/api/project/{project}/alerts/{alert}", a.Auth(a.Alert)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/alerts/reopen", a.Auth(a.ReopenAlerts)).Methods(http.MethodPost)
r.HandleFunc("/api/project/{project}/alerting-rules", a.Auth(a.AlertingRules)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/alerting-rules/export", a.Auth(a.AlertingRulesExport)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/alerting-rules/{rule}", a.Auth(a.AlertingRule)).Methods(http.MethodGet, http.MethodPut, http.MethodDelete)
r.HandleFunc("/api/project/{project}/dashboards", a.Auth(a.Dashboards)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/dashboards/{dashboard}", a.Auth(a.Dashboards)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/panel/data", a.Auth(a.PanelData)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/inspections", a.Auth(a.Inspections)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/application_categories", a.Auth(a.ApplicationCategories)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/custom_applications", a.Auth(a.CustomApplications)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/custom_cloud_pricing", a.Auth(a.CustomCloudPricing)).Methods(http.MethodGet, http.MethodPost, http.MethodDelete)
r.HandleFunc("/api/project/{project}/integrations", a.Auth(a.Integrations)).Methods(http.MethodGet, http.MethodPut)
r.HandleFunc("/api/project/{project}/integrations/{type}", a.Auth(a.Integration)).Methods(http.MethodGet, http.MethodPut, http.MethodDelete, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}", a.Auth(a.Application)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/app/{app}/rca", a.Auth(a.RCA)).Methods(http.MethodGet)
r.HandleFunc("/api/project/{project}/app/{app}/inspection/{type}/config", a.Auth(a.Inspection)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}/instrumentation/{type}", a.Auth(a.Instrumentation)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}/profiling", a.Auth(a.Profiling)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}/tracing", a.Auth(a.Tracing)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}/logs", a.Auth(a.Logs)).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/api/project/{project}/app/{app}/risks", a.Auth(a.Risks)).Methods(http.MethodPost)
r.HandleFunc("/api/project/{project}/node/{node}", a.Auth(a.Node)).Methods(http.MethodGet)
r.PathPrefix("/api/project/{project}/prom/api/v1/{rest:.+}").HandlerFunc(a.Auth(a.Prom))
r.HandleFunc("/.well-known/oauth-protected-resource", a.MCPOAuthProtectedResource).Methods(http.MethodGet)
r.HandleFunc("/.well-known/oauth-authorization-server", a.MCPOAuthAuthorizationServer).Methods(http.MethodGet)
r.HandleFunc("/oauth/register", a.MCPOAuthRegister).Methods(http.MethodPost)
r.HandleFunc("/oauth/authorize", a.MCPOAuthAuthorize).Methods(http.MethodGet, http.MethodPost)
r.HandleFunc("/oauth/token", a.MCPOAuthToken).Methods(http.MethodPost)
r.HandleFunc("/oauth/revoke", a.MCPOAuthRevoke).Methods(http.MethodPost)
r.PathPrefix("/mcp").Handler(a.SetupMCP(api.MCPInstructions).HTTPHandler())
r.HandleFunc("/api/v1/query_range", a.ApiKeyAuth(a.PrometheusQueryRange))
r.HandleFunc("/api/v1/series", a.ApiKeyAuth(a.PrometheusSeries))
r.HandleFunc("/api/v1/metadata", a.ApiKeyAuth(a.PrometheusMetricMetadata))
r.HandleFunc("/api/v1/label/{labelName}/values", a.ApiKeyAuth(a.PrometheusLabelValues))
r.HandleFunc("/api/clickhouse-config", a.ApiKeyAuth(a.ClickhouseConfig)).Methods(http.MethodGet)
r.HandleFunc("/api/clickhouse-connect", a.ClickhouseConnect).Methods(http.MethodGet)
r.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
if a.GetUser(r) == nil {
return
}
statsCollector.RegisterRequest(r)
}).Methods(http.MethodPost)
r.HandleFunc("/stats", func(w http.ResponseWriter, r *http.Request) {
if a.GetUser(r) == nil {
http.Redirect(w, r, cfg.UrlBasePath+"login", http.StatusFound)
return
}
statsCollector.Stats(r, w)
}).Methods(http.MethodGet)
if cfg.DeveloperMode {
r.PathPrefix("/static/").Handler(http.StripPrefix(cfg.UrlBasePath+"static/", http.FileServer(http.Dir("./static"))))
} else {
r.PathPrefix("/static/").Handler(http.StripPrefix(cfg.UrlBasePath, http.FileServer(utils.NewStaticFSWrapper(static))))
}
indexHtml := readIndexHtml(cfg.UrlBasePath, version, instanceUuid, !cfg.DoNotCheckForUpdates, cfg.DefaultTimeRange, cfg.DeveloperMode)
r.PathPrefix("").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write(indexHtml)
})
router.PathPrefix("").Handler(http.RedirectHandler(cfg.UrlBasePath, http.StatusMovedPermanently))
if err = grpcServer.Start(); err != nil {
klog.Exitln("grpc server:", err)
}
if !cfg.HTTPDisabled && cfg.HTTPSListenAddress != "" {
go func() {
klog.Infoln("listening on", cfg.HTTPSListenAddress, "(HTTPS)")
klog.Fatalln(http.ListenAndServeTLS(cfg.HTTPSListenAddress, cfg.TLS.CertFile, cfg.TLS.KeyFile, router))
}()
klog.Infoln("listening on", cfg.ListenAddress)
klog.Fatalln(http.ListenAndServe(cfg.ListenAddress, router))
} else if cfg.HTTPSListenAddress != "" {
klog.Infoln("listening on", cfg.HTTPSListenAddress, "(HTTPS)")
klog.Fatalln(http.ListenAndServeTLS(cfg.HTTPSListenAddress, cfg.TLS.CertFile, cfg.TLS.KeyFile, router))
} else if !cfg.HTTPDisabled {
klog.Infoln("listening on", cfg.ListenAddress)
klog.Fatalln(http.ListenAndServe(cfg.ListenAddress, router))
}
}
func readIndexHtml(basePath, version, instanceUuid string, checkForUpdates bool, defaultTimeRange timeseries.Duration, developerMode bool) []byte {
var (
err error
tpl *template.Template
)
if developerMode {
tpl, err = template.ParseFiles("./static/index.html")
} else {
tpl, err = template.ParseFS(static, "static/index.html")
}
if err != nil {
klog.Exitln(err)
}
buf := bytes.Buffer{}
err = tpl.Execute(&buf, struct {
BasePath string
Version string
InstanceUUID string
CheckForUpdates bool
Edition string
CloudURL string
DefaultTimeRange string
}{
BasePath: basePath,
Version: version,
InstanceUUID: instanceUuid,
CheckForUpdates: checkForUpdates,
Edition: Edition,
CloudURL: cloud.URL,
DefaultTimeRange: defaultTimeRange.ShortString(),
})
if err != nil {
klog.Exitln(err)
}
return buf.Bytes()
}
func setAdminPassword(db *db.DB) error {
fmt.Print("Enter new password: ")
data, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println("")
if err != nil {
return err
}
password := string(data)
if password == "" {
return errors.New("password cannot be blank")
}
fmt.Print("Confirm new password: ")
data, err = term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println("")
if err != nil {
return err
}
confirm := string(data)
if password != confirm {
return errors.New("passwords do not match")
}
err = db.CreateAdminIfNotExists(password)
if err != nil {
return err
}
err = db.SetAdminPassword(password)
if err != nil {
return err
}
return nil
}