Skip to content

Commit

Permalink
Add missing fields for Opsgenie integration (#339)
Browse files Browse the repository at this point in the history
Signed-off-by: SLASHLogin <loginmlgxd@gmail.com>
  • Loading branch information
SLASHLogin authored Oct 5, 2024
1 parent 261728a commit 22ccfe0
Show file tree
Hide file tree
Showing 3 changed files with 452 additions and 59 deletions.
74 changes: 50 additions & 24 deletions docs/services/opsgenie.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,34 @@

To be able to send notifications with argocd-notifications you have to create an [API Integration](https://docs.opsgenie.com/docs/integrations-overview) inside your [Opsgenie Team](https://docs.opsgenie.com/docs/teams).

1. Login to Opsgenie at https://app.opsgenie.com or https://app.eu.opsgenie.com (if you have an account in the european union)
2. Make sure you already have a team, if not follow this guide https://docs.opsgenie.com/docs/teams
3. Click "Teams" in the Menu on the left
4. Select the team that you want to notify
5. In the teams configuration menu select "Integrations"
6. Click "Add Integration" in the top right corner
7. Select "API" integration
8. Give your integration a name, copy the "API key" and safe it somewhere for later
9. Click "Edit" in the integration settings
10. Make sure the checkbox for "Create and Update Access" is selected, disable the other checkboxes to remove unnecessary permissions
11. Click "Save" at the bottom
12. Click "Turn on integration" in the top right corner
13. Check your browser for the correct server apiURL. If it is "app.opsgenie.com" then use the US/international api url `api.opsgenie.com` in the next step, otherwise use `api.eu.opsgenie.com` (European API).
14. You are finished with configuring Opsgenie. Now you need to configure argocd-notifications. Use the apiUrl, the team name and the apiKey to configure the Opsgenie integration in the `argocd-notifications-secret` secret.
15. You can find the example `argocd-notifications-cm` configuration at the below.
1. Login to Opsgenie at https://app.opsgenie.com or https://app.eu.opsgenie.com (if you have an account in the European Union).
2. Make sure you already have a team; if not, follow this guide: https://docs.opsgenie.com/docs/teams.
3. Click "Teams" in the Menu on the left.
4. Select the team that you want to notify.
5. In the team's configuration menu, select "Integrations".
6. Click "Add Integration" in the top right corner.
7. Select "API" integration.
8. Give your integration a name, copy the "API key", and save it somewhere for later.
9. Click "Edit" in the integration settings.
10. Make sure the checkbox for "Create and Update Access" is selected; disable the other checkboxes to remove unnecessary permissions.
11. Click "Save" at the bottom.
12. Click "Turn on integration" in the top right corner.
13. Check your browser for the correct server apiURL. If it is "app.opsgenie.com", then use the US/international API URL `api.opsgenie.com`; otherwise, use `api.eu.opsgenie.com` (European API).
14. You are finished with configuring Opsgenie. Now you need to configure argocd-notifications. Use the apiUrl, the team name, and the apiKey to configure the Opsgenie integration in the `argocd-notifications-secret` secret.
15. You can find the example `argocd-notifications-cm` configuration below.

| **Option** | **Required** | **Type** | **Description** | **Example** |
| ------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------- | -------------------------------- |
| `description` | True | `string` | Description field of the alert that is generally used to provide a detailed information about the alert. | `Hello from Argo CD!` |
| `priority` | False | `string` | Priority level of the alert. Possible values are P1, P2, P3, P4 and P5. Default value is P3. | `P1` |
| `description` | True | `string` | Description field of the alert that is generally used to provide detailed information about the alert. | `Hello from Argo CD!` |
| `priority` | False | `string` | Priority level of the alert. Possible values are P1, P2, P3, P4, and P5. Default value is P3. | `P1` |
| `alias` | False | `string` | Client-defined identifier of the alert, that is also the key element of Alert De-Duplication. | `Life is too short for no alias` |
| `note` | False | `string` | Additional note that will be added while creating the alert. | `Error from Argo CD!` |
| `note` | False | `string` | Additional note that will be added while creating the alert. | `Error from Argo CD!` |
| `actions` | False | `[]string` | Custom actions that will be available for the alert. | `["Resolve", "Escalate"]` |
| `tags` | False | `[]string` | Tags of the alert. | `["critical", "deployment"]` |
| `visibleTo` | False | `[]alert.Responder` | Teams and users that the alert will become visible to without sending any notification. The `type` field is mandatory for each item, where possible values are `team` and `user`. In addition to the `type` field, either `id` or `name` should be provided for teams, and either `id` or `username` should be given for users. Please note that alerts will be visible to the teams specified within the `responders` field by default, so there is no need to re-specify them in the `visibleTo` field. | `[{Type: "team", Id: "team_id"}, {Type: "user", Id: "user_id"}]` |
| `details` | False | `map[string]string` | Map of key-value pairs to use as custom properties of the alert. | `{"environment": "production", "service": "web"}` |
| `entity` | False | `string` | Entity field of the alert that is generally used to specify which domain the alert is related to. | `web-server` |
| `user` | False | `string` | Display name of the request owner. | `admin_user` |

```yaml
apiVersion: v1
Expand All @@ -47,18 +53,38 @@ data:
priority: P1
alias: {{.app.metadata.name}}
note: Error from Argo CD!
actions:
- Restart
- AnExampleAction
tags:
- OverwriteQuietHours
- Critical
visibleTo:
- Id: "{{.app.metadata.responderId}}"
Type: "team"
- Name: "rocket_team"
Type: "team"
- Id: "{{.app.metadata.responderUserId}}"
Type: "user"
- Username: "trinity@opsgenie.com"
Type: "user"
details:
environment: production
service: web
entity: Argo CD Application
user: John Doe
trigger.on-a-problem: |
- description: Application has a problem.
send:
- opsgenie
when: app.status.health.status == 'Degraded' or app.status.operationState.phase in ['Error', 'Failed'] or app.status.sync.status == 'Unknown'
```
16. Add annotation in application yaml file to enable notifications for specific Argo CD app.
16. Add annotation in the application YAML file to enable notifications for a specific Argo CD app.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
notifications.argoproj.io/subscribe.on-a-problem.opsgenie: <your-team>
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
notifications.argoproj.io/subscribe.on-a-problem.opsgenie: <your-team>
```
232 changes: 219 additions & 13 deletions pkg/services/opsgenie.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,27 @@ type OpsgenieOptions struct {
}

type OpsgenieNotification struct {
Description string `json:"description"`
Priority string `json:"priority,omitempty"`
Alias string `json:"alias,omitempty"`
Note string `json:"note,omitempty"`
Description string `json:"description"`
Priority string `json:"priority,omitempty"`
Alias string `json:"alias,omitempty"`
Note string `json:"note,omitempty"`
Actions []string `json:"actions,omitempty"`
Tags []string `json:"tags,omitempty"`
Details map[string]string `json:"details,omitempty"`
Entity string `json:"entity,omitempty"`
VisibleTo []alert.Responder `json:"visibleTo,omitempty"`
User string `json:"user,omitempty"`
}

func (n *OpsgenieNotification) GetTemplater(name string, f texttemplate.FuncMap) (Templater, error) {
desc, err := texttemplate.New(name).Funcs(f).Parse(n.Description)
if err != nil {
return nil, err
}
priority, err := texttemplate.New(name).Funcs(f).Parse(n.Priority)
if err != nil {
return nil, err
}
alias, err := texttemplate.New(name).Funcs(f).Parse(n.Alias)
if err != nil {
return nil, err
Expand All @@ -39,25 +49,189 @@ func (n *OpsgenieNotification) GetTemplater(name string, f texttemplate.FuncMap)
if err != nil {
return nil, err
}
entity, err := texttemplate.New(name).Funcs(f).Parse(n.Entity)
if err != nil {
return nil, err
}
user, err := texttemplate.New(name).Funcs(f).Parse(n.User)
if err != nil {
return nil, err
}

details := make(map[string]*texttemplate.Template)
for key, value := range n.Details {
detailTemplate, err := texttemplate.New(fmt.Sprintf("%s_detail_%s", name, key)).Funcs(f).Parse(value)
if err != nil {
return nil, err
}
details[key] = detailTemplate
}

visibleTo := make([]struct {
Type *texttemplate.Template
Name *texttemplate.Template
Id *texttemplate.Template
Username *texttemplate.Template
}, len(n.VisibleTo))

// Templating for VisibleTo Responder fields (Id, Type, Name, Username)
for i, responder := range n.VisibleTo {
idTemplate, err := texttemplate.New(fmt.Sprintf("%s_responder_%d_id", name, i)).Funcs(f).Parse(responder.Id)
if err != nil {
return nil, err
}
typeTemplate, err := texttemplate.New(fmt.Sprintf("%s_responder_%d_type", name, i)).Funcs(f).Parse(string(responder.Type))
if err != nil {
return nil, err
}
nameTemplate, err := texttemplate.New(fmt.Sprintf("%s_responder_%d_name", name, i)).Funcs(f).Parse(responder.Name)
if err != nil {
return nil, err
}
usernameTemplate, err := texttemplate.New(fmt.Sprintf("%s_responder_%d_username", name, i)).Funcs(f).Parse(responder.Username)
if err != nil {
return nil, err
}

visibleTo[i] = struct {
Type *texttemplate.Template
Name *texttemplate.Template
Id *texttemplate.Template
Username *texttemplate.Template
}{
Type: typeTemplate,
Name: nameTemplate,
Id: idTemplate,
Username: usernameTemplate,
}
}

var actionsTemplates []*texttemplate.Template
if n.Actions != nil {
actionsTemplates = make([]*texttemplate.Template, len(n.Actions))
for i, action := range n.Actions {
actionTemplate, err := texttemplate.New(fmt.Sprintf("%s_action_%d", name, i)).Funcs(f).Parse(action)
if err != nil {
return nil, err
}
actionsTemplates[i] = actionTemplate
}
}

var tagsTemplates []*texttemplate.Template
if n.Tags != nil {
tagsTemplates = make([]*texttemplate.Template, len(n.Tags))
for i, tag := range n.Tags {
tagTemplate, err := texttemplate.New(fmt.Sprintf("%s_tag_%d", name, i)).Funcs(f).Parse(tag)
if err != nil {
return nil, err
}
tagsTemplates[i] = tagTemplate
}
}

return func(notification *Notification, vars map[string]interface{}) error {
if notification.Opsgenie == nil {
notification.Opsgenie = &OpsgenieNotification{}
}

var descData bytes.Buffer
if err := desc.Execute(&descData, vars); err != nil {
return err
}
notification.Opsgenie.Description = descData.String()

var aliasData bytes.Buffer
if err := alias.Execute(&aliasData, vars); err != nil {
return err
}
notification.Opsgenie.Alias = aliasData.String()

var noteData bytes.Buffer
if err := note.Execute(&noteData, vars); err != nil {
return err
}
notification.Opsgenie.Note = noteData.String()

var entityData bytes.Buffer
if err := entity.Execute(&entityData, vars); err != nil {
return err
}
notification.Opsgenie.Entity = entityData.String()

var userData bytes.Buffer
if err := user.Execute(&userData, vars); err != nil {
return err
}
notification.Opsgenie.User = userData.String()

var priorityData bytes.Buffer
if err := priority.Execute(&priorityData, vars); err != nil {
return err
}
notification.Opsgenie.Priority = priorityData.String()

if n.Details != nil {
notification.Opsgenie.Details = make(map[string]string, len(n.Details))
for key, template := range details {
var valueData bytes.Buffer
if err := template.Execute(&valueData, vars); err != nil {
return err
}
notification.Opsgenie.Details[key] = valueData.String()
}
}

if n.VisibleTo != nil {
notification.Opsgenie.VisibleTo = make([]alert.Responder, len(n.VisibleTo))
for i, template := range visibleTo {
var idData, typeData, nameData, usernameData bytes.Buffer

// Execute each responder field template
if err := template.Id.Execute(&idData, vars); err != nil {
return err
}
if err := template.Type.Execute(&typeData, vars); err != nil {
return err
}
if err := template.Name.Execute(&nameData, vars); err != nil {
return err
}
if err := template.Username.Execute(&usernameData, vars); err != nil {
return err
}

notification.Opsgenie.VisibleTo[i] = alert.Responder{
Id: idData.String(),
Type: alert.ResponderType(typeData.String()), // Convert the string to the ResponderType
Name: nameData.String(),
Username: usernameData.String(),
}
}
}

if n.Actions != nil {
notification.Opsgenie.Actions = make([]string, len(n.Actions))
for i, template := range actionsTemplates {
var actionData bytes.Buffer
if err := template.Execute(&actionData, vars); err != nil {
return err
}
notification.Opsgenie.Actions[i] = actionData.String()
}
}

if n.Tags != nil {
notification.Opsgenie.Tags = make([]string, len(n.Tags))
for i, template := range tagsTemplates {
var tagData bytes.Buffer
if err := template.Execute(&tagData, vars); err != nil {
return err
}
notification.Opsgenie.Tags[i] = tagData.String()
}
}

return nil
}, nil
}
Expand All @@ -84,36 +258,68 @@ func (s *opsgenieService) Send(notification Notification, dest Destination) erro
},
})

var description, priority, alias, note string
var description, alias, note, entity, user string
var priority alert.Priority
var actions, tags []string
var details map[string]string
var visibleTo []alert.Responder

if notification.Opsgenie != nil {
if notification.Opsgenie.Description == "" {
return fmt.Errorf("Opsgenie notification description is missing")
return fmt.Errorf("opsgenie notification description is missing")
}

description = notification.Opsgenie.Description

if notification.Opsgenie.Priority != "" {
priority = notification.Opsgenie.Priority
}

if notification.Opsgenie.Alias != "" {
alias = notification.Opsgenie.Alias
}

if notification.Opsgenie.Note != "" {
note = notification.Opsgenie.Note
}
}

alertPriority := alert.Priority(priority)
if notification.Opsgenie.Entity != "" {
entity = notification.Opsgenie.Entity
}

if notification.Opsgenie.User != "" {
user = notification.Opsgenie.User
}

if notification.Opsgenie.Priority != "" {
priority = alert.Priority(notification.Opsgenie.Priority)
}

if len(notification.Opsgenie.Actions) > 0 {
actions = notification.Opsgenie.Actions
}

if len(notification.Opsgenie.Tags) > 0 {
tags = notification.Opsgenie.Tags
}

if len(notification.Opsgenie.Details) > 0 {
details = notification.Opsgenie.Details
}

if len(notification.Opsgenie.VisibleTo) > 0 {
visibleTo = notification.Opsgenie.VisibleTo
}
}

_, err := alertClient.Create(context.TODO(), &alert.CreateAlertRequest{
Message: notification.Message,
Description: description,
Priority: alertPriority,
Priority: priority,
Alias: alias,
Note: note,
Actions: actions,
Tags: tags,
Details: details,
Entity: entity,
VisibleTo: visibleTo,
User: user,
Responders: []alert.Responder{
{
Type: "team",
Expand Down
Loading

0 comments on commit 22ccfe0

Please sign in to comment.