Skip to content

Commit

Permalink
Grafana alerts (#111)
Browse files Browse the repository at this point in the history
* init - grafana alerts

* grafana alert client

* fix typo

* rename

* create, delete

* get

* rename resource

* rename file, list

* update, refactor

* test save progress

* save progress

* grafana alert create int tests

* delete create tests

* cont.

* get tests

* list tests

* update

* update fix, tests, readme, refactor ioutils to io

* add IsPaused

* add note about for field

* update readme

* update changelog

* fix typo

* update workflows
  • Loading branch information
mirii1994 authored Oct 22, 2023
1 parent 0bfa229 commit 252bb97
Show file tree
Hide file tree
Showing 25 changed files with 1,068 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-daily.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
AZURE_CONTAINER_NAME: ${{ secrets.AZURE_CONTAINER_NAME }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
METRICS_FOLDER_ID: ${{ secrets.METRICS_FOLDER_ID }}

GRAFANA_FOLDER_UID: ${{ secrets.GRAFANA_FOLDER_UID }}
GO111MODULE: on
name: Test
runs-on: ubuntu-20.04
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
AZURE_CONTAINER_NAME: ${{ secrets.AZURE_CONTAINER_NAME }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
METRICS_FOLDER_ID: ${{ secrets.METRICS_FOLDER_ID }}
GRAFANA_FOLDER_UID: ${{ secrets.GRAFANA_FOLDER_UID }}
GO111MODULE: on
name: Test
runs-on: ubuntu-20.04
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,20 @@ The library currently supports the following API endpoints:

### Changelog

- v1.18.0
- Add [Grafana Alert Rules API](https://docs.logz.io/api/#tag/Grafana-alerting-provisioning) support.
- v1.17.0
- Add Grafana Folders API.
- Remove deprecated `alerts` (v1).
- v1.16.0
- Add [Grafana Dashboards API](https://docs.logz.io/api/#operation/createDashboard) support.
- v1.15.0
- Add [S3 Fetcher](https://docs.logz.io/api/#tag/Connect-to-S3-Buckets).


<details>
<summary markdown="span">Exapnd to check old versions </summary>

- v1.15.0
- Add [S3 Fetcher](https://docs.logz.io/api/#tag/Connect-to-S3-Buckets).
- v1.14.0
- `alerts_v2` - support new field `schedule`
- v1.13.1
Expand Down
39 changes: 39 additions & 0 deletions grafana_alerts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Grafana Alert

To create a Grafana alert:

```go
data := grafana_alerts.GrafanaAlertQuery{
DatasourceUid: "__expr__",
Model: json.RawMessage(`{"conditions":[{"evaluator":{"params":[0,0],"type":"gt"},"operator":{"type":"and"},"query":{"params":[]},"reducer":{"params":[],"type":"avg"},"type":"query"}],"datasource":{"type":"__expr__","uid":"__expr__"},"expression":"1 == 1","hide":false,"intervalMs":1000,"maxDataPoints":43200,"refId":"A","type":"math"}`),
RefId: "A",
RelativeTimeRange: grafana_alerts.RelativeTimeRangeObj{
From: 0,
To: 0,
},
}

createGrafanaAlert := grafana_alerts.GrafanaAlertRule{
Annotations: map[string]string{"key_test": "value_test"},
Condition: "A",
Data: []*grafana_alerts.GrafanaAlertQuery{&data},
FolderUID: os.Getenv(envGrafanaFolderUid),
NoDataState: grafana_alerts.NoDataOk,
ExecErrState: grafana_alerts.ErrOK,
OrgID: 1,
RuleGroup: "rule_group_1",
Title: "test_alert",
For: int64(3),
}

client, err := grafana_alerts.New(apiToken, server.URL)
grafanaAlert, err := client.CreateGrafanaAlertRule(createGrafanaAlert)
```

| function | func name |
|---------------------|------------------------------------------------------------------------------------------------------------|
| create alert | `func (c *GrafanaAlertClient) CreateGrafanaAlertRule(payload GrafanaAlertRule) (*GrafanaAlertRule, error)` |
| update alert | `func (c *GrafanaAlertClient) UpdateGrafanaAlertRule(payload GrafanaAlertRule) error` |
| delete alert by uid | `func (c *GrafanaAlertClient) DeleteGrafanaAlertRule(uid string) error` |
| get alert by uid | `func (c *GrafanaAlertClient) GetGrafanaAlertRule(uid string) (*GrafanaAlertRule, error)` |
| list alerts | `func (c *GrafanaAlertClient) ListGrafanaAlertRules() ([]GrafanaAlertRule, error)` |
127 changes: 127 additions & 0 deletions grafana_alerts/client_grafana_alert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package grafana_alerts

import (
"fmt"
"github.com/logzio/logzio_terraform_client/client"
"time"
)

type ExecErrState string
type NoDataState string

const (
grafanaAlertServiceEndpoint = "%s/v1/grafana/api/v1/provisioning/alert-rules"

grafanaAlertResourceName = "grafana alert rule"

operationCreateGrafanaAlert = "CreateGrafanaAlert"
operationGetGrafanaAlert = "GetGrafanaAlert"
operationUpdateGrafanaAlert = "UpdateGrafanaAlert"
operationDeleteGrafanaAlert = "DeleteGrafanaAlert"
operationListGrafanaAlerts = "ListGrafanaAlerts"

ErrOK ExecErrState = "OK"
ErrError ExecErrState = "Error"
ErrAlerting ExecErrState = "Alerting"
NoDataOk NoDataState = "OK"
NoData NoDataState = "NoData"
NoDataAlerting NoDataState = "Alerting"
)

type GrafanaAlertClient struct {
*client.Client
}

type GrafanaAlertRule struct {
Annotations map[string]string `json:"annotations,omitempty"`
Condition string `json:"condition"` // Required
Data []*GrafanaAlertQuery `json:"data"` // Required
ExecErrState ExecErrState `json:"execErrState"` // Required
FolderUID string `json:"folderUID"` // Required
For int64 `json:"for"` // Required, representing nanoseconds
Id int64 `json:"id,omitempty"`
Labels map[string]string `json:"labels,omitempty"`
NoDataState NoDataState `json:"noDataState"` // Required
OrgID int64 `json:"orgID"` // Required
Provenance string `json:"provenance,omitempty"`
RuleGroup string `json:"ruleGroup"` // Required
Title string `json:"title"` // Required
Uid string `json:"uid,omitempty"`
Updated time.Time `json:"updated"`
IsPaused bool `json:"isPaused"`
}

type GrafanaAlertQuery struct {
DatasourceUid string `json:"datasourceUid"`
Model interface{} `json:"model"`
QueryType string `json:"queryType"`
RefId string `json:"refId"`
RelativeTimeRange RelativeTimeRangeObj `json:"relativeTimeRange"`
}

type RelativeTimeRangeObj struct {
From time.Duration `json:"from"`
To time.Duration `json:"to"`
}

func New(apiToken string, baseUrl string) (*GrafanaAlertClient, error) {
if len(apiToken) == 0 {
return nil, fmt.Errorf("API token not defined")
}
if len(baseUrl) == 0 {
return nil, fmt.Errorf("Base URL not defined")
}

grafanaAlertClient := &GrafanaAlertClient{
Client: client.New(apiToken, baseUrl),
}

return grafanaAlertClient, nil
}

func validateGrafanaAlertRuleCreateUpdate(payload GrafanaAlertRule, isUpdate bool) error {
if len(payload.Condition) == 0 {
return fmt.Errorf("Field condition must be set!")
}

if payload.Data == nil || len(payload.Data) == 0 {
return fmt.Errorf("Field data must be set!")
}

if len(payload.ExecErrState) == 0 {
return fmt.Errorf("Field execErrState must be set!")
}

if len(payload.FolderUID) == 0 {
return fmt.Errorf("Field folderUID must be set!")
}

if payload.For == 0 {
return fmt.Errorf("Field for must be set!")
}

if len(payload.NoDataState) == 0 {
return fmt.Errorf("Field noDataState must be set!")
}

if len(payload.RuleGroup) == 0 {
return fmt.Errorf("Field ruleGroup must be set!")
}

if len(payload.Title) == 0 {
return fmt.Errorf("Field title must be set!")
}

if isUpdate {
if len(payload.Uid) == 0 {
return fmt.Errorf("Field uid must be set when updating a Grafana alert rule!")
}

} else {
if payload.OrgID == 0 {
return fmt.Errorf("Field orgID must be set!")
}
}

return nil
}
51 changes: 51 additions & 0 deletions grafana_alerts/client_grafana_alert_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package grafana_alerts

import (
"encoding/json"
"fmt"
logzio_client "github.com/logzio/logzio_terraform_client"
"net/http"
)

const (
createGrafanaAlertServiceUrl = grafanaAlertServiceEndpoint
createGrafanaAlertServiceMethod = http.MethodPost
createGrafanaAlertMethodCreated = http.StatusCreated
createGrafanaAlertStatusNotFound = http.StatusNotFound
)

func (c *GrafanaAlertClient) CreateGrafanaAlertRule(payload GrafanaAlertRule) (*GrafanaAlertRule, error) {
err := validateGrafanaAlertRuleCreateUpdate(payload, false)
if err != nil {
return nil, err
}

createGrafanaAlertRuleJson, err := json.Marshal(payload)
if err != nil {
return nil, err
}

res, err := logzio_client.CallLogzioApi(logzio_client.LogzioApiCallDetails{
ApiToken: c.ApiToken,
HttpMethod: createGrafanaAlertServiceMethod,
Url: fmt.Sprintf(createGrafanaAlertServiceUrl, c.BaseUrl),
Body: createGrafanaAlertRuleJson,
SuccessCodes: []int{createGrafanaAlertMethodCreated},
NotFoundCode: createGrafanaAlertStatusNotFound,
ResourceId: nil,
ApiAction: operationCreateGrafanaAlert,
ResourceName: grafanaAlertResourceName,
})

if err != nil {
return nil, err
}

var retVal GrafanaAlertRule
err = json.Unmarshal(res, &retVal)
if err != nil {
return nil, err
}

return &retVal, nil
}
35 changes: 35 additions & 0 deletions grafana_alerts/client_grafana_alert_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package grafana_alerts

import (
"fmt"
logzio_client "github.com/logzio/logzio_terraform_client"
"net/http"
)

const (
deleteGrafanaAlertServiceUrl = grafanaAlertServiceEndpoint + "/%s"
deleteGrafanaAlertServiceMethod = http.MethodDelete
deleteGrafanaAlertServiceSuccess = http.StatusNoContent
// NOTE: the grafana api returns 204 even when you try to delete with a uid that doesn't exist,
// so the following line is just for compatibility with the CallLogzioApi object
deleteGrafanaAlertNotFound = http.StatusNotFound
)

func (c *GrafanaAlertClient) DeleteGrafanaAlertRule(uid string) error {
if uid == "" {
return fmt.Errorf("uid is empty")
}
_, err := logzio_client.CallLogzioApi(logzio_client.LogzioApiCallDetails{
ApiToken: c.ApiToken,
HttpMethod: deleteGrafanaAlertServiceMethod,
Url: fmt.Sprintf(deleteGrafanaAlertServiceUrl, c.BaseUrl, uid),
Body: nil,
SuccessCodes: []int{deleteGrafanaAlertServiceSuccess},
NotFoundCode: deleteGrafanaAlertNotFound,
ResourceId: uid,
ApiAction: operationDeleteGrafanaAlert,
ResourceName: grafanaAlertResourceName,
})

return err
}
41 changes: 41 additions & 0 deletions grafana_alerts/client_grafana_alert_get.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package grafana_alerts

import (
"encoding/json"
"fmt"
logzio_client "github.com/logzio/logzio_terraform_client"
"net/http"
)

const (
getGrafanaAlertServiceUrl = grafanaAlertServiceEndpoint + "/%s"
getGrafanaAlertServiceMethod = http.MethodGet
getGrafanaAlertServiceSuccess = http.StatusOK
getGrafanaAlertServiceNotFound = http.StatusNotFound
)

func (c *GrafanaAlertClient) GetGrafanaAlertRule(uid string) (*GrafanaAlertRule, error) {
res, err := logzio_client.CallLogzioApi(logzio_client.LogzioApiCallDetails{
ApiToken: c.ApiToken,
HttpMethod: getGrafanaAlertServiceMethod,
Url: fmt.Sprintf(getGrafanaAlertServiceUrl, c.BaseUrl, uid),
Body: nil,
SuccessCodes: []int{getGrafanaAlertServiceSuccess},
NotFoundCode: getGrafanaAlertServiceNotFound,
ResourceId: uid,
ApiAction: operationGetGrafanaAlert,
ResourceName: grafanaAlertResourceName,
})

if err != nil {
return nil, err
}

var grafanaAlertRule GrafanaAlertRule
err = json.Unmarshal(res, &grafanaAlertRule)
if err != nil {
return nil, err
}

return &grafanaAlertRule, nil
}
41 changes: 41 additions & 0 deletions grafana_alerts/client_grafana_alert_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package grafana_alerts

import (
"encoding/json"
"fmt"
logzio_client "github.com/logzio/logzio_terraform_client"
"net/http"
)

const (
listGrafanaAlertServiceUrl = grafanaAlertServiceEndpoint
listGrafanaAlertServiceMethod = http.MethodGet
listGrafanaAlertServiceSuccess = http.StatusOK
listGrafanaAlertStatusNotFound = http.StatusNotFound
)

func (c *GrafanaAlertClient) ListGrafanaAlertRules() ([]GrafanaAlertRule, error) {
res, err := logzio_client.CallLogzioApi(logzio_client.LogzioApiCallDetails{
ApiToken: c.ApiToken,
HttpMethod: listGrafanaAlertServiceMethod,
Url: fmt.Sprintf(listGrafanaAlertServiceUrl, c.BaseUrl),
Body: nil,
SuccessCodes: []int{listGrafanaAlertServiceSuccess},
NotFoundCode: listGrafanaAlertStatusNotFound,
ResourceId: nil,
ApiAction: operationListGrafanaAlerts,
ResourceName: grafanaAlertResourceName,
})

if err != nil {
return nil, err
}

var alertRules []GrafanaAlertRule
err = json.Unmarshal(res, &alertRules)
if err != nil {
return nil, err
}

return alertRules, nil
}
Loading

0 comments on commit 252bb97

Please sign in to comment.