Skip to content

Commit f1cfa75

Browse files
committed
Chanel: Allow to send notifications without an active incident
1 parent d688d52 commit f1cfa75

File tree

4 files changed

+81
-73
lines changed

4 files changed

+81
-73
lines changed

internal/channel/channel.go

Lines changed: 24 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@ package channel
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"github.com/icinga/icinga-notifications/internal/config/baseconf"
8-
"github.com/icinga/icinga-notifications/internal/contracts"
97
"github.com/icinga/icinga-notifications/internal/event"
10-
"github.com/icinga/icinga-notifications/internal/recipient"
118
"github.com/icinga/icinga-notifications/pkg/plugin"
129
"go.uber.org/zap"
1310
"go.uber.org/zap/zapcore"
14-
"net/url"
1511
)
1612

1713
type Channel struct {
@@ -158,42 +154,33 @@ func (c *Channel) Restart() {
158154
c.restartCh <- newConfig{c.Type, c.Config}
159155
}
160156

161-
// Notify prepares and sends the notification request, returns a non-error on fails, nil on success
162-
func (c *Channel) Notify(contact *recipient.Contact, i contracts.Incident, ev *event.Event, icingaweb2Url string) error {
163-
p := c.getPlugin()
164-
if p == nil {
165-
return errors.New("plugin could not be started")
157+
// Notify sends the provided notification request to the given *recipient.Contact.
158+
// If the *plugin.Contact field of the specified *plugin.NotificationRequest is not set, it
159+
// automatically determines the contact addresses and sets the notification request contact accordingly.
160+
//
161+
// Returns an error in all the following cases:
162+
// - if the *plugin.Event of the provided notification request is not set,
163+
// - the *plugin.Object of the provided notification request is not set,
164+
// - trying to send a state change event without an associated *plugin.Incident,
165+
// - the corresponding plugin of this channel cannot be started successfully,
166+
// - or fails to successfully deliver the request to the corresponding recipient address(es).
167+
func (c *Channel) Notify(req *plugin.NotificationRequest) error {
168+
if req.Event == nil {
169+
return errors.New("invalid notification request: Event is nil")
166170
}
167-
168-
contactStruct := &plugin.Contact{FullName: contact.FullName}
169-
for _, addr := range contact.Addresses {
170-
contactStruct.Addresses = append(contactStruct.Addresses, &plugin.Address{Type: addr.Type, Address: addr.Address})
171+
if req.Object == nil {
172+
return errors.New("invalid notification request: Object is nil")
173+
}
174+
if req.Contact == nil {
175+
return errors.New("invalid notification request: Contact is nil")
176+
}
177+
if req.Incident == nil && req.Event.Type == event.TypeState {
178+
return errors.New("invalid notification request: cannot send state notification without an incident")
171179
}
172180

173-
baseUrl, _ := url.Parse(icingaweb2Url)
174-
incidentUrl := baseUrl.JoinPath("/notifications/incident")
175-
incidentUrl.RawQuery = fmt.Sprintf("id=%d", i.ID())
176-
object := i.IncidentObject()
177-
178-
req := &plugin.NotificationRequest{
179-
Contact: contactStruct,
180-
Object: &plugin.Object{
181-
Name: object.DisplayName(),
182-
Url: ev.URL,
183-
Tags: object.Tags,
184-
ExtraTags: object.ExtraTags,
185-
},
186-
Incident: &plugin.Incident{
187-
Id: i.ID(),
188-
Url: incidentUrl.String(),
189-
Severity: i.SeverityString(),
190-
},
191-
Event: &plugin.Event{
192-
Time: ev.Time,
193-
Type: ev.Type,
194-
Username: ev.Username,
195-
Message: ev.Message,
196-
},
181+
p := c.getPlugin()
182+
if p == nil {
183+
return errors.New("plugin could not be started")
197184
}
198185

199186
return p.SendNotification(req)

internal/contracts/contracts.go

Lines changed: 0 additions & 14 deletions
This file was deleted.

internal/incident/incident.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ import (
77
"github.com/icinga/icinga-go-library/database"
88
"github.com/icinga/icinga-go-library/types"
99
"github.com/icinga/icinga-notifications/internal/config"
10-
"github.com/icinga/icinga-notifications/internal/contracts"
1110
"github.com/icinga/icinga-notifications/internal/daemon"
1211
"github.com/icinga/icinga-notifications/internal/event"
1312
"github.com/icinga/icinga-notifications/internal/object"
1413
"github.com/icinga/icinga-notifications/internal/recipient"
1514
"github.com/icinga/icinga-notifications/internal/rule"
1615
"github.com/icinga/icinga-notifications/internal/utils"
16+
"github.com/icinga/icinga-notifications/pkg/plugin"
1717
"github.com/jmoiron/sqlx"
1818
"go.uber.org/zap"
19+
"net/url"
1920
"sync"
2021
"time"
2122
)
@@ -75,14 +76,6 @@ func NewIncident(
7576
return i
7677
}
7778

78-
func (i *Incident) IncidentObject() *object.Object {
79-
return i.Object
80-
}
81-
82-
func (i *Incident) SeverityString() string {
83-
return i.Severity.String()
84-
}
85-
8679
func (i *Incident) String() string {
8780
return fmt.Sprintf("#%d", i.Id)
8881
}
@@ -559,14 +552,43 @@ func (i *Incident) triggerEscalations(ctx context.Context, tx *sqlx.Tx, ev *even
559552
// notifyContacts executes all the given pending notifications of the current incident.
560553
// Returns error on database failure or if the provided context is cancelled.
561554
func (i *Incident) notifyContacts(ctx context.Context, ev *event.Event, notifications []*NotificationEntry) error {
555+
baseUrl, err := url.Parse(daemon.Config().Icingaweb2URL)
556+
if err != nil {
557+
i.logger.Errorw("Failed to parse Icinga Web 2 URL", zap.String("url", daemon.Config().Icingaweb2URL), zap.Error(err))
558+
return err
559+
}
560+
561+
incidentUrl := baseUrl.JoinPath("/notifications/incident")
562+
incidentUrl.RawQuery = fmt.Sprintf("id=%d", i.ID())
563+
564+
req := &plugin.NotificationRequest{
565+
Object: &plugin.Object{
566+
Name: i.Object.DisplayName(),
567+
Url: ev.URL,
568+
Tags: i.Object.Tags,
569+
ExtraTags: i.Object.ExtraTags,
570+
},
571+
Incident: &plugin.Incident{
572+
Id: i.Id,
573+
Url: incidentUrl.String(),
574+
Severity: i.Severity.String(),
575+
},
576+
Event: &plugin.Event{
577+
Time: ev.Time,
578+
Type: ev.Type,
579+
Username: ev.Username,
580+
Message: ev.Message,
581+
},
582+
}
583+
562584
for _, notification := range notifications {
563585
contact := i.runtimeConfig.Contacts[notification.ContactID]
564586
if contact == nil {
565587
i.logger.Debugw("Incident refers unknown contact, might got deleted", zap.Int64("contact_id", notification.ContactID))
566588
continue
567589
}
568590

569-
if i.notifyContact(contact, ev, notification.ChannelID) != nil {
591+
if i.notifyContact(contact, req, notification.ChannelID) != nil {
570592
notification.State = NotificationStateFailed
571593
} else {
572594
notification.State = NotificationStateSent
@@ -590,7 +612,7 @@ func (i *Incident) notifyContacts(ctx context.Context, ev *event.Event, notifica
590612
}
591613

592614
// notifyContact notifies the given recipient via a channel matching the given ID.
593-
func (i *Incident) notifyContact(contact *recipient.Contact, ev *event.Event, chID int64) error {
615+
func (i *Incident) notifyContact(contact *recipient.Contact, req *plugin.NotificationRequest, chID int64) error {
594616
ch := i.runtimeConfig.Channels[chID]
595617
if ch == nil {
596618
i.logger.Errorw("Could not find config for channel", zap.Int64("channel_id", chID))
@@ -599,16 +621,21 @@ func (i *Incident) notifyContact(contact *recipient.Contact, ev *event.Event, ch
599621
}
600622

601623
i.logger.Infow(fmt.Sprintf("Notify contact %q via %q of type %q", contact.FullName, ch.Name, ch.Type),
602-
zap.Int64("channel_id", chID), zap.String("event_type", ev.Type))
624+
zap.Int64("channel_id", chID), zap.String("event_type", req.Event.Type))
603625

604-
err := ch.Notify(contact, i, ev, daemon.Config().Icingaweb2URL)
605-
if err != nil {
626+
contactStruct := &plugin.Contact{FullName: contact.FullName}
627+
for _, addr := range contact.Addresses {
628+
contactStruct.Addresses = append(contactStruct.Addresses, &plugin.Address{Type: addr.Type, Address: addr.Address})
629+
}
630+
req.Contact = contactStruct
631+
632+
if err := ch.Notify(req); err != nil {
606633
i.logger.Errorw("Failed to send notification via channel plugin", zap.String("type", ch.Type), zap.Error(err))
607634
return err
608635
}
609636

610637
i.logger.Infow("Successfully sent a notification via channel plugin", zap.String("type", ch.Type),
611-
zap.String("contact", contact.FullName), zap.String("event_type", ev.Type))
638+
zap.String("contact", contact.FullName), zap.String("event_type", req.Event.Type))
612639

613640
return nil
614641
}
@@ -767,7 +794,3 @@ func (e *EscalationState) TableName() string {
767794
type RecipientState struct {
768795
Role ContactRole
769796
}
770-
771-
var (
772-
_ contracts.Incident = (*Incident)(nil)
773-
)

pkg/plugin/plugin.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,11 @@ func FormatMessage(writer io.Writer, req *NotificationRequest) {
310310
}
311311
}
312312

313-
_, _ = fmt.Fprintf(writer, "\nIncident: %s", req.Incident.Url)
313+
if req.Incident != nil {
314+
_, _ = fmt.Fprintf(writer, "\nIncident: %s", req.Incident.Url)
315+
} else {
316+
_, _ = fmt.Fprint(writer, "\nIncident: No active incident found for this object")
317+
}
314318
}
315319

316320
// FormatSubject returns the formatted subject string based on the event type.
@@ -319,8 +323,16 @@ func FormatSubject(req *NotificationRequest) string {
319323
case event.TypeState:
320324
return fmt.Sprintf("[#%d] %s %s is %s", req.Incident.Id, req.Event.Type, req.Object.Name, req.Incident.Severity)
321325
case event.TypeAcknowledgementCleared, event.TypeDowntimeRemoved:
322-
return fmt.Sprintf("[#%d] %s from %s", req.Incident.Id, req.Event.Type, req.Object.Name)
326+
if req.Incident != nil {
327+
return fmt.Sprintf("[#%d] %s from %s", req.Incident.Id, req.Event.Type, req.Object.Name)
328+
}
329+
330+
return fmt.Sprintf("%s from %s", req.Event.Type, req.Object.Name)
323331
default:
324-
return fmt.Sprintf("[#%d] %s on %s", req.Incident.Id, req.Event.Type, req.Object.Name)
332+
if req.Incident != nil {
333+
return fmt.Sprintf("[#%d] %s on %s", req.Incident.Id, req.Event.Type, req.Object.Name)
334+
}
335+
336+
return fmt.Sprintf("%s on %s", req.Event.Type, req.Object.Name)
325337
}
326338
}

0 commit comments

Comments
 (0)