Ctrl K
ComunidadGuías…

main.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}