Skip to content

Commit

Permalink
Send agent context in integration request bodies (#32)
Browse files Browse the repository at this point in the history
* Ensures compatibility with PagerDuty Global Event Rules
  • Loading branch information
rafusel authored Aug 30, 2021
1 parent 202c6c4 commit 3a8c0f2
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 8 deletions.
9 changes: 9 additions & 0 deletions cmd/integrations/nagios/nagios_enqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package nagios
import (
"fmt"
"strings"
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/pkg/eventsapi"
"github.com/spf13/cobra"
)
Expand All @@ -32,6 +34,8 @@ type nagiosEnqueueInput struct {
customFields map[string]string
}

var clock common.Clock = common.NewClock()

var allowedNotificationTypes = []string{"PROBLEM", "ACKNOWLEDGEMENT", "RECOVERY"}
var allowedSourceTypes = []string{"host", "service"}

Expand Down Expand Up @@ -99,6 +103,11 @@ func buildSendEvent(cmdInputs nagiosEnqueueInput) eventsapi.EventV1 {
IncidentKey: cmdInputs.incidentKey,
Description: buildEventDescription(cmdInputs),
Details: cmdutil.StringMapToInterfaceMap(cmdInputs.customFields),
Agent: eventsapi.AgentContext{
QueuedBy: "pd-nagios",
QueuedAt: clock.Now().UTC().Format(time.RFC3339),
AgentId: common.UserAgent(),
},
}
if sendEvent.IncidentKey == "" {
sendEvent.IncidentKey = buildIncidentKey(cmdInputs)
Expand Down
7 changes: 7 additions & 0 deletions cmd/integrations/nagios/nagios_enqueue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/test"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
Expand Down Expand Up @@ -198,6 +199,7 @@ func TestNagiosEnqueue_validInputs(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clock = test.TestClock{}
test.InitConfigForIntegrationsTesting()

defer gock.Off()
Expand Down Expand Up @@ -232,6 +234,11 @@ func TestNagiosEnqueue_validInputs(t *testing.T) {
"incident_key": incidentKey,
"description": buildEventDescription(tt.cmdInputs),
"details": customDetails,
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-nagios",
"queued_at": "2021-01-01T00:00:00Z",
},
}

gock.New(cmdutil.GetDefaults().Address).
Expand Down
10 changes: 10 additions & 0 deletions cmd/integrations/sensu/sensu_enqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"fmt"
"io/ioutil"
"os"
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/pkg/eventsapi"
"github.com/spf13/cobra"
)
Expand All @@ -18,6 +20,8 @@ type sensuCommandInput struct {
checkResult map[string]interface{}
}

var clock common.Clock = common.NewClock()

var errCouldNotReadStdin = errors.New(`could not read stdin for sensu enqueue command`)
var errCheckResultNotValidJson = errors.New("could not unmarshal check result, perhaps stdin did not contain valid JSON")
var errActionNotPresent = errors.New(`could not get event action, set the "action" key`)
Expand Down Expand Up @@ -84,6 +88,12 @@ func buildSendEvent(cmdInput sensuCommandInput) (eventsapi.EventV1, error) {
IncidentKey: dedupKey,
Description: summary,
Details: cmdInput.checkResult,
Client: "Sensu",
Agent: eventsapi.AgentContext{
QueuedBy: "pd-sensu",
QueuedAt: clock.Now().UTC().Format(time.RFC3339),
AgentId: common.UserAgent(),
},
}

return sendEvent, nil
Expand Down
32 changes: 32 additions & 0 deletions cmd/integrations/sensu/sensu_enqueue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/test"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
Expand Down Expand Up @@ -229,6 +230,12 @@ func TestSensuEnqueue_validInputs(t *testing.T) {
},
"client": map[string]interface{}{"name": "clientname"},
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-sensu",
"queued_at": "2021-01-01T00:00:00Z",
},
"client": "Sensu",
},
},
{
Expand All @@ -251,6 +258,12 @@ func TestSensuEnqueue_validInputs(t *testing.T) {
"check": map[string]interface{}{"output": "output"},
"id": "some_id",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-sensu",
"queued_at": "2021-01-01T00:00:00Z",
},
"client": "Sensu",
},
},
{
Expand All @@ -272,6 +285,12 @@ func TestSensuEnqueue_validInputs(t *testing.T) {
"action": "action",
"check": map[string]interface{}{"output": "output"},
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-sensu",
"queued_at": "2021-01-01T00:00:00Z",
},
"client": "Sensu",
},
},
{
Expand All @@ -293,6 +312,12 @@ func TestSensuEnqueue_validInputs(t *testing.T) {
"action": "create",
"check": map[string]interface{}{"output": "output"},
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-sensu",
"queued_at": "2021-01-01T00:00:00Z",
},
"client": "Sensu",
},
},
{
Expand All @@ -314,11 +339,18 @@ func TestSensuEnqueue_validInputs(t *testing.T) {
"action": "resolve",
"check": map[string]interface{}{"output": "output"},
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-sensu",
"queued_at": "2021-01-01T00:00:00Z",
},
"client": "Sensu",
},
},
}

for _, tt := range tests {
clock = test.TestClock{}
t.Run(tt.name, func(t *testing.T) {
test.InitConfigForIntegrationsTesting()

Expand Down
9 changes: 9 additions & 0 deletions cmd/integrations/zabbix/zabbix_enqueue.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import (
"errors"
"fmt"
"strings"
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/pkg/eventsapi"
"github.com/spf13/cobra"
)
Expand All @@ -16,6 +18,8 @@ type zabbixCommandInput struct {
details map[string]string
}

var clock common.Clock = common.NewClock()

var errCouldNotBuildDedupKey = errors.New(`could not build dedupKey, ensure event contains "incident_key", or "id" and "hostname"`)
var errCouldNotBuildSummary = errors.New(`could not build summary, ensure event contains "name", "status", and "hostname"`)

Expand Down Expand Up @@ -59,6 +63,11 @@ func buildSendEvent(cmdInput zabbixCommandInput) (eventsapi.EventV1, error) {
ClientURL: clientUrl,
Description: summary,
Details: cmdutil.StringMapToInterfaceMap(cmdInput.details),
Agent: eventsapi.AgentContext{
QueuedBy: "pd-zabbix",
QueuedAt: clock.Now().UTC().Format(time.RFC3339),
AgentId: common.UserAgent(),
},
}

return sendEvent, nil
Expand Down
27 changes: 27 additions & 0 deletions cmd/integrations/zabbix/zabbix_enqueue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"time"

"github.com/PagerDuty/go-pdagent/pkg/cmdutil"
"github.com/PagerDuty/go-pdagent/pkg/common"
"github.com/PagerDuty/go-pdagent/test"
"github.com/stretchr/testify/assert"
"gopkg.in/h2non/gock.v1"
Expand Down Expand Up @@ -134,6 +135,11 @@ func TestZabbixEnqueue_validInputs(t *testing.T) {
"status": "{TRIGGER.STATUS}",
"hostname": "{HOST.NAME}",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-zabbix",
"queued_at": "2021-01-01T00:00:00Z",
},
},
},
{
Expand All @@ -157,6 +163,11 @@ func TestZabbixEnqueue_validInputs(t *testing.T) {
"status": "{TRIGGER.STATUS}",
"hostname": "{HOST.NAME}",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-zabbix",
"queued_at": "2021-01-01T00:00:00Z",
},
},
},
{
Expand All @@ -182,6 +193,11 @@ func TestZabbixEnqueue_validInputs(t *testing.T) {
"hostname": "{HOST.NAME}",
"NOTE": "Escalation cancelled (converted from trigger to resolve by pdagent integration)",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-zabbix",
"queued_at": "2021-01-01T00:00:00Z",
},
},
},
{
Expand Down Expand Up @@ -209,6 +225,11 @@ func TestZabbixEnqueue_validInputs(t *testing.T) {
"hostname": "{HOST.NAME}",
"url": "some.url",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-zabbix",
"queued_at": "2021-01-01T00:00:00Z",
},
},
},
{
Expand All @@ -235,12 +256,18 @@ func TestZabbixEnqueue_validInputs(t *testing.T) {
"hostname": "{HOST.NAME}",
"someErrantKeyHere": "someErrantKeyHere",
},
"agent": map[string]interface{}{
"agent_id": common.UserAgent(),
"queued_by": "pd-zabbix",
"queued_at": "2021-01-01T00:00:00Z",
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
clock = test.TestClock{}
test.InitConfigForIntegrationsTesting()

defer gock.Off()
Expand Down
16 changes: 16 additions & 0 deletions pkg/common/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package common

import "time"

type Clock interface {
Now() time.Time
}

type realClock struct{}

func NewClock() Clock {
c := &realClock{}
return c
}

func (realClock) Now() time.Time { return time.Now() }
24 changes: 16 additions & 8 deletions pkg/eventsapi/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ const endpointV1 = "/generic/2010-04-15/create_event.json"

// EventV1 corresponds to a V1 event object.
type EventV1 struct {
ServiceKey string `json:"service_key"`
EventType string `json:"event_type"`
IncidentKey string `json:"incident_key,omitempty"`
Description string `json:"description"`
Details DetailsV1 `json:"details,omitempty"`
Client string `json:"client,omitempty"`
ClientURL string `json:"client_url,omitempty"`
Contexts []ContextV1 `json:"contexts,omitempty"`
ServiceKey string `json:"service_key"`
EventType string `json:"event_type"`
IncidentKey string `json:"incident_key,omitempty"`
Description string `json:"description"`
Details DetailsV1 `json:"details,omitempty"`
Client string `json:"client,omitempty"`
ClientURL string `json:"client_url,omitempty"`
Contexts []ContextV1 `json:"contexts,omitempty"`
Agent AgentContext `json:"agent,omitempty"`
}

func (e *EventV1) GetRoutingKey() string {
Expand All @@ -40,6 +41,13 @@ func (e *EventV1) Version() EventVersion {
// DetailsV1 corresponds to a V1 details object.
type DetailsV1 map[string]interface{}

// AgentContext is used for pdagent integrations
type AgentContext struct {
QueuedBy string `json:"queued_by"`
QueuedAt string `json:"queued_at"`
AgentId string `json:"agent_id"`
}

// ContextV1 corresponds to a V1 context object.
//
// Technically this can either be a `link` or `image` context type, but
Expand Down
7 changes: 7 additions & 0 deletions test/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package test

import "time"

type TestClock struct{}

func (TestClock) Now() time.Time { return time.Date(2021, time.January, 1, 0, 0, 0, 0, time.UTC) }

0 comments on commit 3a8c0f2

Please sign in to comment.