Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for opsgenie #73

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2da7a9c
feat: add support for opsgenie
JonasPf Nov 9, 2020
3530a13
Fix for issue https://github.com/containrrr/shoutrrr/issues/71 (#75)
Nov 15, 2020
c78b769
docs: add atighineanu as a contributor (#81)
allcontributors[bot] Nov 15, 2020
b67f9b1
added custom port option for rocketchat (#80)
Nov 15, 2020
d3c987d
fix for https://github.com/containrrr/shoutrrr/issues/70 (#74)
ellisab Nov 15, 2020
ca5e1f2
docs: add ellisab as a contributor (#82)
allcontributors[bot] Nov 15, 2020
bdbdcf4
do not rewrite channel name without hashes (#85)
ellisab Nov 17, 2020
0f4ed7c
Added few testcases for rawURL passed as path/#####channel and path/…
Nov 24, 2020
54f0f63
refactor: rename JSON to AlertPayload
JonasPf Nov 27, 2020
c4ba3b9
refactor: rename `plugin` to `service`
JonasPf Nov 27, 2020
c108686
feat: add support for setting parameters via query string
JonasPf Dec 10, 2020
88d19ae
Fix query parameter documentation
JonasPf Dec 28, 2020
5277afa
Implement GetURL
JonasPf Dec 28, 2020
6bee1e0
Merge remote-tracking branch 'upstream/master' into opsgenie
JonasPf Jan 2, 2021
3a4d5c6
Add opsgenie to servicemap
JonasPf Jan 6, 2021
993bce8
Support generating and verifying opsgenie URLs
JonasPf Jan 6, 2021
cddfbf8
Use OpsGenie convention for URL params instead of json
JonasPf Jan 25, 2021
453c63c
Merge branch 'main' into opsgenie
JonasPf Jan 25, 2021
5cb1ad3
Use 'key:value,key2:value2' format for details and overall code cleanup
JonasPf Jan 26, 2021
584deee
Remove comment about spaces in URL
JonasPf Feb 2, 2021
b168211
Remove comment about spaces; Add test instead
JonasPf Feb 2, 2021
33fddea
Merge branch 'main' into opsgenie
JonasPf Feb 2, 2021
59819e7
Fix IFTTT test by removing obsolete values; Add another test for values
JonasPf Feb 4, 2021
a511157
Merge branch 'main' into opsgenie
JonasPf Feb 4, 2021
d87e5d4
Fix teams test by removing obsolete key without value
JonasPf Feb 4, 2021
3145f46
Fix OpsGenie test by using URL encoded strings for space, comma, colon
JonasPf Feb 4, 2021
ec73129
Simplify `getURL` by using query.Set/Encode instead of diy-ing it
JonasPf Feb 4, 2021
cc9d75d
Fix test after simplifying `getURL`, it encodes commas etc. now
JonasPf Feb 4, 2021
6390b12
Move `newAlertPayload` to `Service` struct
JonasPf Feb 4, 2021
8f3aac4
Preparation for implementing an interface for de-serializing structs
JonasPf Feb 4, 2021
3305147
Fix linting issues
JonasPf Feb 4, 2021
7bd4ba0
feat: add support for struct and map fields
piksel Feb 7, 2021
2fc0dbc
Fix: Use default port if no port was specified
JonasPf Feb 16, 2021
7e19f85
Merge branch 'feature/format-struct-map-fields' into opsgenie
JonasPf Feb 16, 2021
26bd837
Remove custom serialization logic; use formatters functionality instead
JonasPf Feb 16, 2021
38c1f10
Fix: Expect default port (443) in test
JonasPf Feb 16, 2021
f0c460d
Fix test: formatter lowercases keys
JonasPf Feb 16, 2021
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
18 changes: 18 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,24 @@
"doc",
"code"
]
},
{
"login": "atighineanu",
"name": "Alexei Tighineanu",
"avatar_url": "https://avatars1.githubusercontent.com/u/27206712?v=4",
"profile": "https://github.com/atighineanu",
"contributions": [
"code"
]
},
{
"login": "ellisab",
"name": "Alexandru Bonini",
"avatar_url": "https://avatars2.githubusercontent.com/u/1402047?v=4",
"profile": "https://github.com/ellisab",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ Heavily inspired by <a href="https://github.com/caronc/apprise">caronc/apprise</
[![github code size in bytes](https://img.shields.io/github/languages/code-size/containrrr/shoutrrr.svg?style=flat-square)](https://github.com/containrrr/shoutrrr)
[![license](https://img.shields.io/github/license/containrrr/shoutrrr.svg?style=flat-square)](https://github.com/containrrr/shoutrrr/blob/master/LICENSE)
[![godoc](https://godoc.org/github.com/containrrr/shoutrrr?status.svg)](https://godoc.org/github.com/containrrr/shoutrrr) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-)

[![All Contributors](https://img.shields.io/badge/all_contributors-8-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

</div>
Expand Down Expand Up @@ -84,12 +83,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/MrLuje"><img src="https://avatars0.githubusercontent.com/u/632075?v=4" width="100px;" alt=""/><br /><sub><b>MrLuje</b></sub></a><br /><a href="https://github.com/containrrr/shoutrrr/commits?author=MrLuje" title="Code">💻</a> <a href="https://github.com/containrrr/shoutrrr/commits?author=MrLuje" title="Documentation">📖</a></td>
<td align="center"><a href="http://simme.dev"><img src="https://avatars0.githubusercontent.com/u/1596025?v=4" width="100px;" alt=""/><br /><sub><b>Simon Aronsson</b></sub></a><br /><a href="https://github.com/containrrr/shoutrrr/commits?author=simskij" title="Code">💻</a> <a href="https://github.com/containrrr/shoutrrr/commits?author=simskij" title="Documentation">📖</a> <a href="#maintenance-simskij" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://arnested.dk"><img src="https://avatars2.githubusercontent.com/u/190005?v=4" width="100px;" alt=""/><br /><sub><b>Arne Jørgensen</b></sub></a><br /><a href="https://github.com/containrrr/shoutrrr/commits?author=arnested" title="Documentation">📖</a> <a href="https://github.com/containrrr/shoutrrr/commits?author=arnested" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/atighineanu"><img src="https://avatars1.githubusercontent.com/u/27206712?v=4" width="100px;" alt=""/><br /><sub><b>Alexei Tighineanu</b></sub></a><br /><a href="https://github.com/containrrr/shoutrrr/commits?author=atighineanu" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ellisab"><img src="https://avatars2.githubusercontent.com/u/1402047?v=4" width="100px;" alt=""/><br /><sub><b>Alexandru Bonini</b></sub></a><br /><a href="https://github.com/containrrr/shoutrrr/commits?author=ellisab" title="Code">💻</a></td>
</tr>
</table>

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
60 changes: 60 additions & 0 deletions docs/services/opsgenie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# OpsGenie

## URL Format

## Creating a REST API endpoint in OpsGenie

1. Open up the Integration List page by clicking on *Settings => Integration List* within the menu
![Screenshot 1](opsgenie/1.png)

2. Click *API => Add*

3. Make sure *Create and Update Access* and *Enabled* are checked and click *Save Integration*
![Screenshot 2](opsgenie/2.png)

4. Copy the *API Key*

5. Format the service URL

The host can be either api.opsgenie.com or api.eu.opsgenie.com depending on the location of your instance. See
the [OpsGenie documentation](https://docs.opsgenie.com/docs/alert-api) for details.

```
opsgenie://api.opsgenie.com/eb243592-faa2-4ba2-a551q-1afdf565c889
└───────────────────────────────────┘
token
```

## Passing parameters via code

If you want to, you can pass additional parameters to the `send` function.
<br/>
The following example contains all parameters that are currently supported.

```gotemplate
service.Send("An example alert message", &types.Params{
"alias": "Life is too short for no alias",
"description": "Every alert needs a description",
"responders": `[{"id":"4513b7ea-3b91-438f-b7e4-e3e54af9147c","type":"team"},{"name":"NOC","type":"team"}]`,
"visibleTo": `[{"id":"4513b7ea-3b91-438f-b7e4-e3e54af9147c","type":"team"},{"name":"rocket_team","type":"team"}]`,
"actions": "An action",
"tags": "tag1 tag2",
"details": `{"key1": "value1", "key2": "value2"}`,
"entity": "An example entity",
"source": "The source",
"priority": "P1",
"user": "Dracula",
"note": "Here is a note",
})
```

# Optional parameters

You can optionally specify the parameters in the URL:
pushover://shoutrrr:token@userKey/?devices=device&title=Custom+Title&priority=1
JonasPf marked this conversation as resolved.
Show resolved Hide resolved

Example using the command line:

shoutrrr send -u 'opsgenie://api.eu.opsgenie.com/token?tags=["tag1","tag2"]&description=testing&responders=[{"username":"superuser", "type": "user"}]&entity=Example Entity&source=Example Source&actions=["asdf", "bcde"]' -m "Hello World6"


Binary file added docs/services/opsgenie/1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/services/opsgenie/2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions docs/services/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Click on the service for a more thorough explanation.

| Service | URL format |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| [Discord](./discord.md) | *discord://__`token`__@__`id`__* |
| [Discord](./discord.md) | *discord://__`token`__@__`id`__* |
| [Telegram](./not-documented.md) | *telegram://__`token`__@telegram?channels=__`channel-1`__[,__`channel-2`__,...]* |
| [Pushover](./pushover.md) | *pushover://shoutrrr:__`apiToken`__@__`userKey`__/?devices=__`device1`__[,__`device2`__, ...]* |
| [Slack](./not-documented.md) | *slack://[__`botname`__@]__`token-a`__/__`token-b`__/__`token-c`__* |
Expand All @@ -17,4 +17,5 @@ Click on the service for a more thorough explanation.
| [Hangouts Chat](./hangouts.md) | *hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz* |
| [Zulip Chat](./zulip.md) | *zulip://__`bot-mail`__:__`bot-key`__@__`zulip-domain`__/?stream=__`name-or-id`__&topic=__`name`__* |
| [Join](./not-documented.md) | *join://shoutrrr:__`api-key`__@join/?devices=__`device1`__[,__`device2`__, ...][&icon=__`icon`__][&title=__`title`__]* |
| [Rocketchat](./rocketchat.md) | *rocketchat://[__`username`__@]__`rocketchat-host`__/__`token`__[/__`channel`&#124;`@recipient`__]* |
| [Rocketchat](./rocketchat.md) | *rocketchat://[__`username`__@]__`rocketchat-host`__/__`token`__[/__`channel`&#124;`@recipient`__]* |
| [OpsGenie](./opsgenie.md) | *opsgenie://__`opsgenie-host`__/__`apikey`__* |
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ require (
github.com/mitchellh/mapstructure v1.2.2 // indirect
github.com/onsi/ginkgo v1.8.0
github.com/onsi/gomega v1.5.0
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
github.com/pelletier/go-toml v1.7.0 // indirect
github.com/sirupsen/logrus v1.2.0
github.com/spf13/afero v1.2.2 // indirect
Expand All @@ -20,6 +17,9 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.6.3
golang.org/x/net v0.0.0-20190522155817-f3200d17e092
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
Expand Down
2 changes: 2 additions & 0 deletions pkg/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/containrrr/shoutrrr/pkg/services/teams"
"github.com/containrrr/shoutrrr/pkg/services/telegram"
"github.com/containrrr/shoutrrr/pkg/services/zulip"
"github.com/containrrr/shoutrrr/pkg/services/opsgenie"
t "github.com/containrrr/shoutrrr/pkg/types"
"github.com/containrrr/shoutrrr/pkg/xmpp"
)
Expand Down Expand Up @@ -150,6 +151,7 @@ var serviceMap = map[string]func() t.Service{
"zulip": func() t.Service { return &zulip.Service{} },
"join": func() t.Service { return &join.Service{} },
"rocketchat": func() t.Service { return &rocketchat.Service{} },
"opsgenie": func() t.Service { return &opsgenie.Service{} },
}

func (router *ServiceRouter) initService(rawURL string) (t.Service, error) {
Expand Down
72 changes: 72 additions & 0 deletions pkg/services/opsgenie/opsgenie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package opsgenie

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"

"github.com/containrrr/shoutrrr/pkg/services/standard"
"github.com/containrrr/shoutrrr/pkg/types"
)

const (
alertEndpointTemplate = "https://%s/v2/alerts"
)

// Service providing OpsGenie as a notification service
type Service struct {
standard.Standard
config *Config
}

func (service *Service) sendAlert(url string, apiKey string, payload AlertPayload) error {
jsonBody, err := json.Marshal(payload)
if err != nil {
return err
}

jsonBuffer := bytes.NewBuffer(jsonBody)

req, err := http.NewRequest("POST", url, jsonBuffer)
if err != nil {
return err
}
req.Header.Add("Authorization", "GenieKey "+apiKey)
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send notification to OpsGenie: %s", err)
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("OpsGenie notification returned %d HTTP status code. Cannot read body: %s", resp.StatusCode, err)
}
return fmt.Errorf("OpsGenie notification returned %d HTTP status code: %s", resp.StatusCode, body)
}

return nil
}

// Initialize loads ServiceConfig from configURL and sets logger for this Service
func (service *Service) Initialize(configURL *url.URL, logger *log.Logger) error {
service.Logger.SetLogger(logger)
service.config = &Config{}
err := service.config.SetURL(configURL)
return err
}

// Send a notification message to OpsGenie
// See: https://docs.opsgenie.com/docs/alert-api#create-alert
func (service *Service) Send(message string, params *types.Params) error {
config := service.config
url := fmt.Sprintf(alertEndpointTemplate, config.Host)
payload := NewAlertPayload(message, config, params)
return service.sendAlert(url, config.ApiKey, payload)
}
98 changes: 98 additions & 0 deletions pkg/services/opsgenie/opsgenie_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package opsgenie

import (
"fmt"
"net/url"

"github.com/containrrr/shoutrrr/pkg/services/standard"
)

// Config for use within the opsgenie service
type Config struct {
ApiKey string `desc:"The OpsGenie API key"`
Host string `desc:"The OpsGenie API host. Use 'api.opsgenie.com' for US and 'api.eu.opsgenie.com' for EU instances"`
standard.EnumlessConfig
Alias string
Description string
Responders string
VisibleTo string
Actions string
Tags string
Details string
Entity string
Source string
Priority string
Note string
User string
}

// QueryFields returns the fields that are part of the Query of the service URL
func (config *Config) QueryFields() []string {
return []string{
"alias",
"responders",
"description",
"visibleTo",
"actions",
"tags",
"details",
"entity",
"source",
"priority",
"note",
"user",
}
}

// Set updates the value of a Query field
func (config *Config) Set(key string, value string) error {
switch key {
case "alias":
config.Alias = value
case "description":
config.Description = value
case "responders":
config.Responders = value
case "visibleTo":
config.VisibleTo = value
case "actions":
config.Actions = value
case "tags":
config.Tags = value
case "details":
config.Details = value
case "entity":
config.Entity = value
case "source":
config.Source = value
case "priority":
config.Priority = value
case "note":
config.Note = value
case "user":
config.User = value
default:
return fmt.Errorf("invalid query key \"%s\"", key)
}

return nil
}

// SetURL updates a ServiceConfig from a URL representation of it's field values
func (config *Config) SetURL(url *url.URL) error {
config.Host = url.Hostname() + ":" + url.Port()
config.ApiKey = url.Path[1:]

for key, vals := range url.Query() {
if err := config.Set(key, vals[0]); err != nil {
return err
}
}

return nil
}

JonasPf marked this conversation as resolved.
Show resolved Hide resolved
const (
// Scheme is the identifying part of this service's configuration URL
Scheme = "opsgenie"
)
Loading