api/ctx.go
Rama main · réplica de lectura del repositorio original.
package api
import (
"fmt"
"github.com/coroot/coroot/api/views"
"github.com/coroot/coroot/api/views/cloud"
"github.com/coroot/coroot/api/views/overview"
"github.com/coroot/coroot/cache"
"github.com/coroot/coroot/db"
"github.com/coroot/coroot/model"
"github.com/coroot/coroot/utils"
"github.com/dustin/go-humanize/english"
)
type DataWithContext struct {
Context Context `json:"context"`
Data any `json:"data"`
}
type Context struct {
Status Status `json:"status"`
Search Search `json:"search"`
Incidents map[model.ApplicationCategory]int `json:"incidents"`
Alerts map[string]int `json:"alerts"`
Fluxcd *GitOpsStatus `json:"fluxcd"`
Argocd *GitOpsStatus `json:"argocd"`
License *License `json:"license,omitempty"`
Multicluster bool `json:"multicluster"`
MemberProjects []string `json:"member_projects,omitempty"`
}
type GitOpsStatus struct {
Issues int `json:"issues"`
}
type Status struct {
Status model.Status `json:"status"`
Error string `json:"error"`
Prometheus Prometheus `json:"prometheus"`
NodeAgent NodeAgent `json:"node_agent"`
KubeStateMetrics *KubeStateMetrics `json:"kube_state_metrics"`
Clouds []CloudStatus `json:"clouds"`
}
type CloudStatus struct {
Id string `json:"id"`
Name string `json:"name"`
Status model.Status `json:"status"`
Message string `json:"message"`
}
type Prometheus struct {
Status model.Status `json:"status"`
Message string `json:"message"`
Error string `json:"error"`
Action string `json:"action"`
}
type NodeAgent struct {
Status model.Status `json:"status"`
Nodes int `json:"nodes"`
}
type KubeStateMetrics struct {
Status model.Status `json:"status"`
Applications int `json:"applications"`
}
type Search struct {
Applications []Application `json:"applications"`
Nodes []Node `json:"nodes"`
}
type Application struct {
Id model.ApplicationId `json:"id"`
}
type Node struct {
Name string `json:"name"`
ClusterId string `json:"cluster_id"`
}
type License struct {
Invalid bool `json:"invalid"`
Message string `json:"message"`
}
type LicenseManager interface {
CheckLicense() *License
}
func (api *Api) WithContext(p *db.Project, cacheStatus *cache.Status, w *model.World, data any) DataWithContext {
if p == nil {
return DataWithContext{}
}
alerts, _ := api.db.GetFiringAlertCountsBySeverity(p.Id)
if alerts == nil {
alerts = map[string]int{}
}
res := DataWithContext{
Context: Context{
Status: renderStatus(p, cacheStatus, w, api.globalPrometheus),
Search: renderSearch(w),
Incidents: renderIncidents(w),
Alerts: alerts,
Fluxcd: gitOpsStatus(w, w != nil && w.Flux != nil, overview.CountFluxIssues),
Argocd: gitOpsStatus(w, w != nil && w.ArgoCD != nil, overview.CountArgoCDIssues),
Multicluster: p.Multicluster(),
MemberProjects: p.Settings.MemberProjects,
},
Data: data,
}
if lm := api.licenseMgr; lm != nil {
if l := lm.CheckLicense(); l != nil {
res.Context.License = l
if l.Invalid {
res.Data = nil
}
}
}
return res
}
func renderIncidents(w *model.World) map[model.ApplicationCategory]int {
res := map[model.ApplicationCategory]int{}
if w == nil {
return res
}
for _, app := range w.Applications {
if len(app.Incidents) == 0 {
continue
}
if last := app.Incidents[len(app.Incidents)-1]; !last.Resolved() {
res[app.Category]++
}
}
return res
}
func gitOpsStatus(w *model.World, present bool, count func(*model.World) int) *GitOpsStatus {
if !present {
return nil
}
return &GitOpsStatus{Issues: count(w)}
}
func renderStatus(p *db.Project, cacheStatus *cache.Status, w *model.World, globalPrometheus *db.IntegrationPrometheus) Status {
res := Status{
Status: model.OK,
}
if p == nil {
res.Status = model.WARNING
res.Error = "Project not found"
return res
}
res.Prometheus.Status = model.OK
res.Prometheus.Message = "ok"
promCfg := p.PrometheusConfig(globalPrometheus)
refreshInterval := max(promCfg.RefreshInterval, cache.MinRefreshInterval)
switch {
case promCfg.Url == "" && !promCfg.UseClickHouse && !p.Multicluster():
res.Prometheus.Status = model.WARNING
res.Prometheus.Message = "Prometheus is not configured."
res.Prometheus.Action = "configure"
case cacheStatus != nil && cacheStatus.Error != "":
res.Prometheus.Status = model.WARNING
res.Prometheus.Message = "An error has been occurred while querying Prometheus:"
res.Prometheus.Error = cacheStatus.Error
res.Prometheus.Action = "configure"
case cacheStatus != nil && cacheStatus.LagMax > 5*refreshInterval:
lag := utils.FormatDuration(cacheStatus.LagAvg, 1)
res.Prometheus.Status = model.WARNING
res.Prometheus.Message = fmt.Sprintf("The Prometheus cache lag is %s, likely due to a restart or upgrade. Synchronization is in progress.", lag)
res.Prometheus.Action = "wait"
}
if res.Prometheus.Status >= model.WARNING {
res.Status = model.WARNING
}
if w == nil {
return res
}
is := w.IntegrationStatus
if !is.NodeAgent.Installed {
res.NodeAgent.Status = model.WARNING
res.Status = model.WARNING
} else {
res.NodeAgent.Status = model.OK
res.NodeAgent.Nodes = len(w.Nodes)
}
if is.KubeStateMetrics.Required {
res.KubeStateMetrics = &KubeStateMetrics{}
if is.KubeStateMetrics.Installed {
res.KubeStateMetrics.Status = model.OK
res.KubeStateMetrics.Applications = len(w.Applications) // TODO: count k8s apps only,
} else {
res.KubeStateMetrics.Status = model.WARNING
res.Status = model.WARNING
}
}
for _, c := range []struct {
id, name string
view *cloud.View
}{
{"aws", "AWS", views.AWS(w, p.Settings.Integrations.AWS != nil)},
{"gcp", "GCP", views.GCP(w)},
{"oci", "OCI", views.OCI(w)},
} {
cs := CloudStatus{Id: c.id, Name: c.name}
switch {
case !c.view.Configured && !c.view.Detected:
continue
case !c.view.Configured:
cs.Status = model.INFO
cs.Message = fmt.Sprintf("the cluster runs on %s, but the integration is not configured: its managed databases are not discovered", c.name)
case len(c.view.Errors) > 0:
cs.Status = model.WARNING
cs.Message = english.Plural(len(c.view.Errors), "discovery error", "") + " reported by the cluster-agent"
res.Status = model.WARNING
default:
cs.Status = model.OK
cs.Message = english.Plural(len(c.view.Instances), "instance", "") + " discovered"
}
res.Clouds = append(res.Clouds, cs)
}
return res
}
func renderSearch(w *model.World) Search {
search := Search{}
if w == nil {
return search
}
for _, app := range w.Applications {
search.Applications = append(search.Applications, Application{
Id: app.Id,
})
}
for _, node := range w.Nodes {
search.Nodes = append(search.Nodes, Node{
Name: node.GetName(),
ClusterId: node.ClusterId,
})
}
return search
}