Skip to content
This repository has been archived by the owner on Jul 19, 2022. It is now read-only.

Commit

Permalink
new flag
Browse files Browse the repository at this point in the history
  • Loading branch information
betorvs committed Apr 14, 2021
1 parent 7ef5656 commit 4a4b127
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 12 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ Versioning](http://semver.org/spec/v2.0.0.html).

## Unreleased

## [1.0.4] - 2021-04-14
### Added
- flags `--heartbeat` and `--hearbeat-map` to enable this handler to send an heartbeat request to opsgenie instead opening an alert. This can be used as keepalived handler or just to check if one integration is working fine.

### Changed
- Update Readme

## [1.0.3] - 2021-04-01
### Added
- flag `--tagTemplate` based on fork from [nixwiz](https://github.com/nixwiz/sensu-opsgenie-handler/commit/73208fff8c51234814758e8aafed114751daeff9). Remove short option due conflict with titlePrettify flag.
Expand Down
75 changes: 65 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@
![Go Test](https://github.com/betorvs/sensu-opsgenie-handler/workflows/Go%20Test/badge.svg)
[![Sensu Bonsai Asset](https://img.shields.io/badge/Bonsai-Download%20Me-brightgreen.svg?colorB=89C967&logo=sensu)](https://bonsai.sensu.io/assets/betorvs/sensu-opsgenie-handler)

## Table of Contents
- [Overview](#overview)
- [Configuration](#configuration)
- [Usage examples](#usage-examples)
- [Others Configurations](#others-configurations)
- [To use Opsgenie Priority from Entity or Check](#to-use-opsgenie-priority-from-entity-or-check)
- [Argument Annotations](#argument-annotations)
- [Asset registration](#asset-registration)
- [Installation from source](#installation-from-source)
- [Additional notes](#additional-notes)
- [Option remediation handler](#option-remediation-handler)
- [Option keepalived handler](#option-keepalived-handler)
- [Contributing](#contributing)

## Overview

The Sensu Go OpsGenie Handler is a [Sensu Event Handler][3] which manages
[OpsGenie][2] incidents, for alerting operators. With this handler,
[Sensu][1] can trigger OpsGenie incidents.
Expand All @@ -10,15 +26,6 @@ This handler was inspired by [pagerduty plugin][6].

After version 1.0.0 we changed opsgenie [sdk][7] to [sdk-v2][8].

## Installation

Download the latest version of the sensu-opsgenie-handler from [releases][4],
or create an executable script from this source.

From the local path of the sensu-opsgenie-handler repository:
```
go build -o /usr/local/bin/sensu-opsgenie-handler main.go
```

## Configuration

Expand Down Expand Up @@ -105,6 +112,8 @@ Flags:
-d, --descriptionTemplate string The template for the description to be sent (default "{{.Check.Output}}")
--escalation-team string The OpsGenie Escalation Responders Team, use default from OPSGENIE_ESCALATION_TEAM env var
-F, --fullDetails Include the more details to send to OpsGenie like proxy_entity_name, occurrences and agent details arch and os
--hearbeat-map string Map of entity/check to heartbeat name. E. entity/check=heartbeat_name,entity1/check1=heartbeat
--heartbeat Enable Heartbeat Events
-h, --help help for sensu-opsgenie-handler
-i, --includeEventInNote Include the event JSON in the payload sent to OpsGenie
-l, --messageLimit int The maximum length of the message field (default 130)
Expand All @@ -130,6 +139,8 @@ Use "sensu-opsgenie-handler [command] --help" for more information about a comma

To configure OpsGenie Sensu Integration follow these first part in [OpsGenie Docs][5].

## Others Configurations

### To use Opsgenie Priority from Entity or Check

Please add this annotations inside sensu-agent:
Expand Down Expand Up @@ -179,7 +190,7 @@ metadata:
```


### Asset creation
### Asset registration

The easiest way to get this handler added to your Sensu environment, is to add it as an asset from Bonsai:

Expand All @@ -189,8 +200,25 @@ sensuctl asset add betorvs/sensu-opsgenie-handler --rename sensu-opsgenie-handle

See `sensuctl asset --help` for details on how to specify version.

## Installation from source

Download the latest version of the sensu-opsgenie-handler from [releases][4],
or create an executable script from this source.

From the local path of the sensu-opsgenie-handler repository:
```
go build -o /usr/local/bin/sensu-opsgenie-handler main.go
```


## Additional notes

Both options presented here changes how this handler works, only use this for specific cases and remember to not apply any filter, because in both cases, they will send only in case of status `!= 0`.

### Option remediation handler

Using this option we enable opsgenie handler to add extra properties in a previous alert created with remediation actions (checks).

Flags: `--remediation-events` and `--remediation-event-alias`. More info about [remediation][11].

Proposed Workflow:
Expand Down Expand Up @@ -241,6 +269,31 @@ spec:

More ideas about remediation try this [plugin][12].

### Option keepalived handler

This option enable opsgenie plugin to send heatbeat pings instead creating new alerts. This options could fit in keepalive for important network assets or important integrations (like [alert manager plugin][14]).

Flags `--heartbeat` and `--hearbeat-map` can map a entity/check to a [heartbeat][13] in opsgenie.

And Handler:
```yml
type: Handler
api_version: core/v2
metadata:
name: opsgenie_heartbeat
namespace: default
spec:
type: pipe
command: sensu-opsgenie-handler --heartbeat --hearbeat-map webserver01/check-nginx=heartbeat_webserver01_nginx
env_vars:
- OPSGENIE_REGION=us
timeout: 10
runtime_assets:
- betorvs/sensu-opsgenie-handler
filters: null
```


## Contributing

See https://github.com/sensu/sensu-go/blob/master/CONTRIBUTING.md
Expand All @@ -257,3 +310,5 @@ See https://github.com/sensu/sensu-go/blob/master/CONTRIBUTING.md
[10]: https://docs.sensu.io/sensu-go/latest/guides/secrets-management/#use-env-for-secrets-management
[11]: https://github.com/sensu/sensu-remediation-handler
[12]: https://github.com/betorvs/sensu-dynamic-check-mutator
[13]: https://docs.opsgenie.com/docs/heartbeat-api
[14]: https://github.com/betorvs/sensu-alertmanager-events
120 changes: 118 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/opsgenie/opsgenie-go-sdk-v2/alert"
"github.com/opsgenie/opsgenie-go-sdk-v2/client"
"github.com/opsgenie/opsgenie-go-sdk-v2/heartbeat"
"github.com/sensu-community/sensu-plugin-sdk/sensu"
"github.com/sensu-community/sensu-plugin-sdk/templates"

Expand Down Expand Up @@ -44,6 +45,8 @@ type Config struct {
TagsTemplates []string
RemediationEvents bool
RemediationEventAlias string
HeartbeatEvents bool
HeartbeatMap string
}

var (
Expand Down Expand Up @@ -246,6 +249,24 @@ var (
Usage: "Replace opsgenie alias with this value and add only output as node in opsgenie. Should be used with auto remediation checks",
Value: &plugin.RemediationEventAlias,
},
{
Path: "heartbeat",
Env: "",
Argument: "heartbeat",
Shorthand: "",
Default: false,
Usage: "Enable Heartbeat Events",
Value: &plugin.HeartbeatEvents,
},
{
Path: "hearbeat-map",
Env: "",
Argument: "hearbeat-map",
Shorthand: "",
Default: "",
Usage: "Map of entity/check to heartbeat name. E. entity/check=heartbeat_name,entity1/check1=heartbeat",
Value: &plugin.HeartbeatMap,
},
}
)

Expand Down Expand Up @@ -448,12 +469,12 @@ func executeHandler(event *types.Event) error {
if err != nil {
return fmt.Errorf("failed to create opsgenie client: %s", err)
}

// always create an alert in opsgenie if status != 0
if event.Check.Status != 0 {
return createIncident(alertClient, event)
}

// if RemediationEvents true
// if RemediationEvents true: change behaviour of opsgenie plugin
if plugin.RemediationEvents && event.Check.Status == 0 {
hasAlert, _ := getAlert(alertClient, plugin.RemediationEventAlias)
details := make(map[string]string)
Expand All @@ -464,6 +485,33 @@ func executeHandler(event *types.Event) error {
notes := fmt.Sprintf("%s ", event.Check.Output)
return updateAlert(alertClient, notes, hasAlert, details)
}
// if heartbeat true: match entity/check with heartbeat
if plugin.HeartbeatEvents && event.Check.Status == 0 && plugin.HeartbeatMap != "" {
heartbeats, err := parseHeartbeatMap(plugin.HeartbeatMap)
if err != nil {
return err
}
entity_check := fmt.Sprintf("%s/%s", event.Entity.Name, event.Check.Name)
if heartbeats[entity_check] != "" {
fmt.Printf("Pinging heartbeat %s \n", heartbeats[entity_check])
errPing := pingHeartbeat(heartbeats[entity_check])
if errPing != nil {
return errPing
}
}
if heartbeats["all"] != "" {
// ping all alerts
fmt.Printf("Pinging heartbeat %s without entity/check defined\n", heartbeats["all"])
errPing := pingHeartbeat(heartbeats["all"])
if errPing != nil {
return errPing
}
}
if len(heartbeats) != 0 {
fmt.Println("Not pinging any heartbeat because entity/check defined do not match")
}
return nil
}

// check if event has a alert
_, alias, _ := parseEventKeyTags(event)
Expand Down Expand Up @@ -660,3 +708,71 @@ func updateAlert(alertClient *alert.Client, notes string, alertid string, detail
}
return nil
}

func pingHeartbeat(name string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
heartbeatClient, err := heartbeat.NewClient(&client.Config{
ApiKey: plugin.AuthToken,
OpsGenieAPIURL: switchOpsgenieRegion(),
})
if err != nil {
return err
}

hearbeatResult, err := heartbeatClient.Ping(ctx, name)
if err != nil {
return err
}
fmt.Printf("Heartbeat %s was requested with %s and response time %v and message %s", name, hearbeatResult.RequestId, hearbeatResult.ResponseTime, hearbeatResult.Message)

return nil
}

func parseHeartbeatMap(s string) (map[string]string, error) {
// entity1/check1=heartbeat1,entity2/check2=heartbeat2
heartbeats := make(map[string]string)
temp := makeMap(s)
for k, v := range temp {
if strings.Contains(v, "/") {
return heartbeats, fmt.Errorf("hearbeat wrong format: entity/check=heartbeat_name")
}
if k != "" && v != "" {
key := k
if !strings.Contains(k, "/") {
key = "all"
}
heartbeats[key] = v
}
}
return heartbeats, nil
}

func makeMap(s string) map[string]string {
temp := make(map[string]string)
if strings.Contains(s, ",") {
splited := strings.Split(s, ",")
for _, v := range splited {
a, b := splitString(v, "=")
if a != "" && b != "" {
temp[a] = b
}
}
} else {
a, b := splitString(s, "=")
if a != "" && b != "" {
temp[a] = b
}
}
return temp
}

func splitString(s, div string) (string, string) {
if div != "" {
splited := strings.Split(s, div)
if len(splited) == 2 {
return splited[0], splited[1]
}
}
return "", ""
}
44 changes: 44 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,47 @@ func TestVisibilityTeams(t *testing.T) {
res1 := visibilityTeams()
assert.Equal(t, res1, test1)
}

func TestSplitString(t *testing.T) {
test1 := "key1=value1"
res1 := "key1"
res2 := "value1"
val1, val2 := splitString(test1, "=")
assert.Equal(t, res1, val1)
assert.Equal(t, res2, val2)
assert.NotEqual(t, res1, val2)
}

func TestMakeRewriteAnnotation(t *testing.T) {
test1 := "key1=value1,key2=value2"
res1 := map[string]string{"key1": "value1", "key2": "value2"}
val1 := makeMap(test1)
assert.Equal(t, res1, val1)
test2 := "key1=value1,key2/subkey2=value2"
res2 := map[string]string{"key1": "value1", "key2/subkey2": "value2"}
val2 := makeMap(test2)
assert.Equal(t, res2, val2)
test3 := "key1=value1,key2=value2,"
res3 := map[string]string{"key1": "value1", "key2": "value2"}
val3 := makeMap(test3)
assert.Equal(t, res3, val3)
}

func TestParseHeartbeatMap(t *testing.T) {
test1 := "entity1/check1=heartbeat1,entity2/check2=heartbeat2"
expected1 := map[string]string{"entity1/check1": "heartbeat1", "entity2/check2": "heartbeat2"}
res1, err1 := parseHeartbeatMap(test1)
assert.Equal(t, expected1, res1)
assert.NoError(t, err1)

test2 := "entity1/check1=heartbeat1,"
expected2 := map[string]string{"entity1/check1": "heartbeat1"}
res2, err2 := parseHeartbeatMap(test2)
assert.Equal(t, expected2, res2)
assert.NoError(t, err2)

test3 := "heartbeat1=entity1/check1,"
_, err3 := parseHeartbeatMap(test3)
assert.Error(t, err3)

}

0 comments on commit 4a4b127

Please sign in to comment.