Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
!/cluster/testdata/*.yml
!/config/testdata/*.yml
!/examples/ha/tls/*.yml
!/examples/slack-update-messages.yml
!/notify/email/testdata/*.yml
!/doc/examples/simple.yml
!/circle.yml
Expand Down
3 changes: 3 additions & 0 deletions config/notifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@ type SlackConfig struct {
LinkNames bool `yaml:"link_names" json:"link_names,omitempty"`
MrkdwnIn []string `yaml:"mrkdwn_in,omitempty" json:"mrkdwn_in,omitempty"`
Actions []*SlackAction `yaml:"actions,omitempty" json:"actions,omitempty"`
// UpdateMessage enables updating existing Slack messages instead of creating new ones.
// Requires bot token with chat:write scope. Webhook URLs do not support updates.
UpdateMessage bool `yaml:"update_message" json:"update_message,omitempty"`
// Timeout is the maximum time allowed to invoke the slack. Setting this to 0
// does not impose a timeout.
Timeout time.Duration `yaml:"timeout" json:"timeout"`
Expand Down
37 changes: 37 additions & 0 deletions examples/slack-update-messages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Alertmanager configuration with Slack message updates
global:
resolve_timeout: 5m

# Route configuration - required!
route:
receiver: 'slack'
group_by: ['alertname', 'cluster', 'service']
group_wait: 10s
group_interval: 10s
repeat_interval: 12h

receivers:
- name: 'slack'
slack_configs:
- send_resolved: true # Required for message updates!
api_url: 'https://slack.com/api/chat.postMessage'
http_config:
authorization:
credentials: 'xoxb-your-bot-token'
channel: '#your-channel'
update_message: true # This enables message updates!

# Template for updated messages
title: '{{ .GroupLabels.alertname }} - {{ .Status | toUpper }}'
text: |
{{ if eq .Status "firing" }}
πŸ”₯ *{{ .Alerts.Firing | len }} alert(s) firing*
{{ else }}
βœ… *All alerts resolved*
{{ end }}

{{ range .Alerts }}
β€’ {{ if .Annotations.summary }}{{ .Annotations.summary }}{{ else }}{{ .Labels.alertname }}{{ end }}
{{ end }}

color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
3 changes: 3 additions & 0 deletions nflog/nflogpb/nflog.proto
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ message Entry {
repeated uint64 firing_alerts = 6;
// ResolvedAlerts list of hashes of resolved alerts at the last notification time.
repeated uint64 resolved_alerts = 7;
// Metadata holds integration-specific metadata (e.g. Slack message_ts, Jira issue key).
// This allows integrations to store identifiers for updating existing notifications.
map<string, string> metadata = 8;
}

// MeshEntry is a wrapper message to communicate a notify log
Expand Down
82 changes: 82 additions & 0 deletions notify/metadata_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2024 Prometheus Team
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright 2024 Prometheus Team
// Copyright 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 notify

import (
"fmt"
"sync"

"github.com/prometheus/alertmanager/nflog/nflogpb"
)

// MetadataStore is a temporary in-memory store for notification metadata
// (e.g., Slack message_ts, Jira issue keys) until protobuf Entry supports metadata field.
// This allows integrations to update existing notifications instead of creating new ones.
type MetadataStore struct {
mtx sync.RWMutex
data map[string]map[string]string // key: stateKey(groupKey, receiver) -> metadata map
}

// NewMetadataStore creates a new MetadataStore.
func NewMetadataStore() *MetadataStore {
return &MetadataStore{
data: make(map[string]map[string]string),
}
}

// Set stores metadata for a given receiver and group key.
func (s *MetadataStore) Set(receiver *nflogpb.Receiver, groupKey string, metadata map[string]string) {
s.mtx.Lock()
defer s.mtx.Unlock()

key := stateKey(groupKey, receiver)
s.data[key] = metadata
}

// Get retrieves metadata for a given receiver and group key.
func (s *MetadataStore) Get(receiver *nflogpb.Receiver, groupKey string) (map[string]string, bool) {
s.mtx.RLock()
defer s.mtx.RUnlock()

key := stateKey(groupKey, receiver)
metadata, ok := s.data[key]
return metadata, ok
}

// Delete removes metadata for a given receiver and group key.
func (s *MetadataStore) Delete(receiver *nflogpb.Receiver, groupKey string) {
s.mtx.Lock()
defer s.mtx.Unlock()

key := stateKey(groupKey, receiver)
delete(s.data, key)
}

// stateKey returns a string key for a log entry consisting of the group key and receiver.
// This matches the key generation in nflog.
func stateKey(gkey string, r *nflogpb.Receiver) string {
return receiverKey(gkey, r)
}

// receiverKey creates a unique key from group key and receiver.
// Format matches nflog's internal stateKey format: "groupKey:groupName/integration/idx"

Check failure on line 73 in notify/metadata_store.go

View workflow job for this annotation

GitHub Actions / lint

Comment should end in a period (godot)
func receiverKey(groupKey string, r *nflogpb.Receiver) string {
return groupKey + ":" + receiverString(r)
}

// receiverString returns a string representation of the receiver.
func receiverString(r *nflogpb.Receiver) string {
return fmt.Sprintf("%s/%s/%d", r.GroupName, r.Integration, r.Idx)
}

Check failure on line 82 in notify/metadata_store.go

View workflow job for this annotation

GitHub Actions / lint

File is not properly formatted (gofumpt)
Loading
Loading