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(opsgenie): Add support for setting alias in Opsgenie notification #268

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 38 additions & 5 deletions docs/services/opsgenie.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@ To be able to send notifications with argocd-notifications you have to create an
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
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. Make sure the checkboxes for "Create and Update Access" and "enable" are selected, disable the other checkboxes to remove unnecessary permissions
10. Click "Safe Integration" at the bottom
11. 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).
12. 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.
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.

| **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` |
| `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` |

```yaml
apiVersion: v1
Expand All @@ -26,4 +34,29 @@ data:
apiUrl: <api-url>
apiKeys:
<your-team>: <integration-api-key>
template.opsgenie: |
message: |
[Argo CD] Application {{.app.metadata.name}} has a problem.
opsgenie:
description: |
Application: {{.app.metadata.name}}
Health Status: {{.app.status.health.status}}
Operation State Phase: {{.app.status.operationState.phase}}
Sync Status: {{.app.status.sync.status}}
priority: P1
alias: {{.app.metadata.name}}
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.
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
notifications.argoproj.io/subscribe.on-a-problem.opsgenie: <your-team>
```
16 changes: 16 additions & 0 deletions pkg/services/opsgenie.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@
type OpsgenieNotification struct {
Description string `json:"description"`
Priority string `json:"priority,omitempty"`
Alias string `json:"alias,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
}
alias, err := texttemplate.New(name).Funcs(f).Parse(n.Alias)
if err != nil {
return nil, err
}
return func(notification *Notification, vars map[string]interface{}) error {
if notification.Opsgenie == nil {
notification.Opsgenie = &OpsgenieNotification{}
Expand All @@ -38,6 +43,11 @@
return err
}
notification.Opsgenie.Description = descData.String()
var aliasData bytes.Buffer
if err := alias.Execute(&aliasData, vars); err != nil {
return err
}

Check warning on line 49 in pkg/services/opsgenie.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/opsgenie.go#L48-L49

Added lines #L48 - L49 were not covered by tests
notification.Opsgenie.Alias = aliasData.String()
return nil
}, nil
}
Expand Down Expand Up @@ -65,6 +75,7 @@
})
description := ""
priority := ""
alias := ""

Check warning on line 78 in pkg/services/opsgenie.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/opsgenie.go#L78

Added line #L78 was not covered by tests
if notification.Opsgenie != nil {
if notification.Opsgenie.Description == "" {
return fmt.Errorf("Opsgenie notification description is missing")
Expand All @@ -75,6 +86,10 @@
if notification.Opsgenie.Priority != "" {
priority = notification.Opsgenie.Priority
}

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

Check warning on line 92 in pkg/services/opsgenie.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/opsgenie.go#L90-L92

Added lines #L90 - L92 were not covered by tests
}

alertPriority := alert.Priority(priority)
Expand All @@ -83,6 +98,7 @@
Message: notification.Message,
Description: description,
Priority: alertPriority,
Alias: alias,

Check warning on line 101 in pkg/services/opsgenie.go

View check run for this annotation

Codecov / codecov/patch

pkg/services/opsgenie.go#L101

Added line #L101 was not covered by tests
Responders: []alert.Responder{
{
Type: "team",
Expand Down
75 changes: 66 additions & 9 deletions pkg/services/opsgenie_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ func TestOpsgenieNotification_GetTemplater(t *testing.T) {
// Prepare test data
name := "testTemplate"
descriptionTemplate := "Test Opsgenie alert: {{.foo}}"
aliasTemplate := "Test alias: {{.foo}}"
f := texttemplate.FuncMap{}

t.Run("ValidTemplate", func(t *testing.T) {
// Create a new OpsgenieNotification instance
notification := OpsgenieNotification{
Description: descriptionTemplate,
Alias: aliasTemplate,
}

// Call the GetTemplater method
Expand All @@ -66,9 +68,12 @@ func TestOpsgenieNotification_GetTemplater(t *testing.T) {

// Assert that the OpsgenieNotification's description field was correctly updated
assert.Equal(t, "Test Opsgenie alert: bar", mockNotification.Opsgenie.Description)

// Assert that the OpsgenieNotification's alias field was correctly updated
assert.Equal(t, "Test alias: bar", mockNotification.Opsgenie.Alias)
})

t.Run("InvalidTemplate", func(t *testing.T) {
t.Run("InvalidTemplateDescription", func(t *testing.T) {
// Create a new OpsgenieNotification instance with an invalid description template
notification := OpsgenieNotification{
Description: "{{.invalid", // Invalid template syntax
Expand All @@ -80,6 +85,19 @@ func TestOpsgenieNotification_GetTemplater(t *testing.T) {
// Assert that an error occurred during the call
assert.Error(t, err)
})

t.Run("InvalidTemplateAlias", func(t *testing.T) {
// Create a new OpsgenieNotification instance with an invalid alias template
notification := OpsgenieNotification{
Alias: "{{.invalid", // Invalid template syntax
}

// Call the GetTemplater method with the invalid template
_, err := notification.GetTemplater(name, f)

// Assert that an error occurred during the call
assert.Error(t, err)
})
}

func TestOpsgenie_SendNotification_MissingAPIKey(t *testing.T) {
Expand All @@ -89,24 +107,22 @@ func TestOpsgenie_SendNotification_MissingAPIKey(t *testing.T) {
}))
defer server.Close()

// Replace the HTTP client in the Opsgenie service with a mock client
mockClient := &http.Client{
Transport: &http.Transport{},
}
service := NewOpsgenieServiceWithClient(OpsgenieOptions{
service := NewOpsgenieService(OpsgenieOptions{
ApiUrl: server.URL,
ApiKeys: map[string]string{}}, mockClient)
ApiKeys: map[string]string{}})

// Prepare test data
recipient := "testRecipient"
message := "Test message"
descriptionTemplate := "Test Opsgenie alert: {{.foo}}"
aliasTemplate := "Test alias: {{.foo}}"

// Create test notification with description
notification := Notification{
Message: message,
Opsgenie: &OpsgenieNotification{
Description: descriptionTemplate,
Alias: aliasTemplate,
},
}

Expand All @@ -115,9 +131,9 @@ func TestOpsgenie_SendNotification_MissingAPIKey(t *testing.T) {

// Assert the result for missing API Key
assert.Error(t, err)
assert.Contains(t, err.Error(), "No API key configured for recipient")
assert.Contains(t, err.Error(), "no API key configured for recipient testRecipient")
}
func TestOpsgenie_SendNotification_MissingDescriptionAndPriority(t *testing.T) {
func TestOpsgenie_SendNotification_WithMessageOnly(t *testing.T) {
// Create a mock HTTP server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -227,3 +243,44 @@ func TestOpsgenie_SendNotification_WithDescriptionAndPriority(t *testing.T) {
// Assert the result for description and priority present
assert.NoError(t, err) // Expect no error
}

func TestOpsgenie_SendNotification_WithAllFields(t *testing.T) {
// Create a mock HTTP server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer server.Close()

// Replace the HTTP client in the Opsgenie service with a mock client
mockClient := &http.Client{
Transport: &http.Transport{},
}
service := NewOpsgenieServiceWithClient(OpsgenieOptions{
ApiUrl: server.URL,
ApiKeys: map[string]string{
"testRecipient": "testApiKey",
}}, mockClient)

// Prepare test data
recipient := "testRecipient"
message := "Test message"
descriptionTemplate := "Test Opsgenie alert: {{.foo}}"
aliasTemplate := "Test alias: {{.foo}}"
priority := "P1"

// Create test notification with description and priority
notification := Notification{
Message: message,
Opsgenie: &OpsgenieNotification{
Description: descriptionTemplate,
Priority: priority,
Alias: aliasTemplate,
},
}

// Execute the service method with description and priority
err := service.Send(notification, Destination{Recipient: recipient, Service: "opsgenie"})

// Assert the result for description and priority present
assert.NoError(t, err) // Expect no error
}
Loading