mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
feat(api): expose all silences under /silences.json
This commit is contained in:
@@ -101,6 +101,7 @@ func setupRouter(router *gin.Engine) {
|
||||
router.GET(getViewURL("/autocomplete.json"), autocomplete)
|
||||
router.GET(getViewURL("/labelNames.json"), knownLabelNames)
|
||||
router.GET(getViewURL("/labelValues.json"), knownLabelValues)
|
||||
router.GET(getViewURL("/silences.json"), silences)
|
||||
|
||||
router.GET(getViewURL("/custom.css"), customCSS)
|
||||
router.GET(getViewURL("/custom.js"), customJS)
|
||||
|
||||
@@ -409,3 +409,79 @@ func autocomplete(c *gin.Context) {
|
||||
c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte))
|
||||
logAlertsView(c, "MIS", time.Since(start))
|
||||
}
|
||||
|
||||
func silences(c *gin.Context) {
|
||||
noCache(c)
|
||||
|
||||
dedupedSilences := []models.ManagedSilence{}
|
||||
|
||||
showExpired := false
|
||||
showExpiredValue, found := c.GetQuery("showExpired")
|
||||
if found && showExpiredValue == "1" {
|
||||
showExpired = true
|
||||
}
|
||||
|
||||
searchTerm := ""
|
||||
searchTermValue, found := c.GetQuery("searchTerm")
|
||||
if found && searchTermValue != "" {
|
||||
searchTerm = strings.ToLower(searchTermValue)
|
||||
}
|
||||
|
||||
for _, silence := range alertmanager.DedupSilences() {
|
||||
if silence.IsExpired && !showExpired {
|
||||
continue
|
||||
}
|
||||
if searchTerm != "" {
|
||||
isMatch := false
|
||||
if strings.Contains(strings.ToLower(silence.Silence.Comment), searchTerm) {
|
||||
isMatch = true
|
||||
} else if strings.Contains(strings.ToLower(silence.Silence.CreatedBy), searchTerm) {
|
||||
isMatch = true
|
||||
} else {
|
||||
for _, match := range silence.Silence.Matchers {
|
||||
eq := "="
|
||||
if match.IsRegex {
|
||||
eq = "=~"
|
||||
}
|
||||
if searchTerm == fmt.Sprintf("%s%s\"%s\"", strings.ToLower(match.Name), eq, strings.ToLower(match.Value)) {
|
||||
isMatch = true
|
||||
} else if searchTerm == fmt.Sprintf("%s%s%s", match.Name, eq, match.Value) {
|
||||
isMatch = true
|
||||
} else if strings.Contains(strings.ToLower(match.Name), searchTerm) {
|
||||
isMatch = true
|
||||
} else if strings.Contains(strings.ToLower(match.Value), searchTerm) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isMatch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
dedupedSilences = append(dedupedSilences, silence)
|
||||
}
|
||||
|
||||
recentFirst := true
|
||||
sortReverse, found := c.GetQuery("sortReverse")
|
||||
if found && sortReverse == "1" {
|
||||
recentFirst = false
|
||||
}
|
||||
|
||||
sort.Slice(dedupedSilences, func(i int, j int) bool {
|
||||
if dedupedSilences[i].Silence.EndsAt.Equal(dedupedSilences[j].Silence.EndsAt) {
|
||||
if dedupedSilences[i].Silence.StartsAt.Equal(dedupedSilences[j].Silence.StartsAt) {
|
||||
return dedupedSilences[i].Silence.ID < dedupedSilences[j].Silence.ID
|
||||
}
|
||||
return dedupedSilences[i].Silence.StartsAt.After(dedupedSilences[j].Silence.StartsAt) == recentFirst
|
||||
}
|
||||
return dedupedSilences[i].Silence.EndsAt.Before(dedupedSilences[j].Silence.EndsAt) == recentFirst
|
||||
})
|
||||
|
||||
data, err := json.Marshal(dedupedSilences)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data(http.StatusOK, gin.MIMEJSON, data)
|
||||
}
|
||||
|
||||
@@ -584,3 +584,27 @@ func TestValidateAuthorFromHeaders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSilences(t *testing.T) {
|
||||
mockConfig()
|
||||
for _, version := range mock.ListAllMocks() {
|
||||
t.Logf("Validating silences.json response using mock files from Alertmanager %s", version)
|
||||
mockAlerts(version)
|
||||
r := ginTestEngine()
|
||||
req := httptest.NewRequest("GET", "/silences.json?showExpired=1&sortReverse=1&searchTerm=a", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
r.ServeHTTP(resp, req)
|
||||
if resp.Code != http.StatusOK {
|
||||
t.Errorf("GET /silences.json returned status %d", resp.Code)
|
||||
}
|
||||
ur := []models.ManagedSilence{}
|
||||
body := resp.Body.Bytes()
|
||||
err := json.Unmarshal(body, &ur)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to unmarshal response: %s", err)
|
||||
}
|
||||
if len(ur) != 3 {
|
||||
t.Errorf("Incorrect number of silences: got %d, wanted 3", len(ur))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@@ -32,6 +32,7 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/go-playground/colors.v1 v1.2.0
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787
|
||||
|
||||
@@ -2,6 +2,7 @@ package alertmanager
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/prymitive/karma/internal/config"
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
@@ -94,6 +95,40 @@ func DedupAlerts() []models.AlertGroup {
|
||||
return dedupedGroups
|
||||
}
|
||||
|
||||
// DedupKnownLabels returns a deduplicated slice of all known label names
|
||||
func DedupSilences() []models.ManagedSilence {
|
||||
silenceByCluster := map[string]map[string]models.Silence{}
|
||||
upstreams := GetAlertmanagers()
|
||||
|
||||
for _, am := range upstreams {
|
||||
for id, silence := range am.Silences() {
|
||||
cluster := am.ClusterID()
|
||||
|
||||
if _, found := silenceByCluster[cluster]; !found {
|
||||
silenceByCluster[cluster] = map[string]models.Silence{}
|
||||
}
|
||||
|
||||
if _, ok := silenceByCluster[cluster][id]; !ok {
|
||||
silenceByCluster[cluster][id] = silence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dedupedSilences := []models.ManagedSilence{}
|
||||
for cluster, silenceMap := range silenceByCluster {
|
||||
for _, silence := range silenceMap {
|
||||
managedSilence := models.ManagedSilence{
|
||||
Cluster: cluster,
|
||||
IsExpired: silence.EndsAt.Before(now),
|
||||
Silence: silence,
|
||||
}
|
||||
dedupedSilences = append(dedupedSilences, managedSilence)
|
||||
}
|
||||
}
|
||||
return dedupedSilences
|
||||
}
|
||||
|
||||
// DedupColors returns a color map merged from all Alertmanager upstream color
|
||||
// maps
|
||||
func DedupColors() models.LabelsColorMap {
|
||||
|
||||
@@ -24,3 +24,10 @@ type Silence struct {
|
||||
JiraID string `json:"jiraID"`
|
||||
JiraURL string `json:"jiraURL"`
|
||||
}
|
||||
|
||||
// ManagedSilence is a standalone silence detached from any alert
|
||||
type ManagedSilence struct {
|
||||
Cluster string `json:"cluster"`
|
||||
IsExpired bool `json:"isExpired"`
|
||||
Silence Silence `json:"silence"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user