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

Grafana alerts #111

Merged
merged 26 commits into from
Oct 22, 2023
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
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value duplication in delete get list crate update may be worth unifying those. Not critical for your consideration :)

)

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value duplication in get list may be worth unifying those. Not critical for your consideration :)

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Value duplication in get list update may be worth unifying those. Not critical for your consideration :)

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
Loading