diff --git a/.github/workflows/test-daily.yml b/.github/workflows/test-daily.yml index b0642b6..9d6c332 100644 --- a/.github/workflows/test-daily.yml +++ b/.github/workflows/test-daily.yml @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c12907b..f44302a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -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 diff --git a/README.md b/README.md index 0b90456..f2bc7e7 100644 --- a/README.md +++ b/README.md @@ -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).
Exapnd to check old versions +- 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 diff --git a/grafana_alerts/README.md b/grafana_alerts/README.md new file mode 100644 index 0000000..571804f --- /dev/null +++ b/grafana_alerts/README.md @@ -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)` | diff --git a/grafana_alerts/client_grafana_alert.go b/grafana_alerts/client_grafana_alert.go new file mode 100644 index 0000000..2eb8101 --- /dev/null +++ b/grafana_alerts/client_grafana_alert.go @@ -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 +} diff --git a/grafana_alerts/client_grafana_alert_create.go b/grafana_alerts/client_grafana_alert_create.go new file mode 100644 index 0000000..c8ec368 --- /dev/null +++ b/grafana_alerts/client_grafana_alert_create.go @@ -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 +} diff --git a/grafana_alerts/client_grafana_alert_delete.go b/grafana_alerts/client_grafana_alert_delete.go new file mode 100644 index 0000000..5b2eaab --- /dev/null +++ b/grafana_alerts/client_grafana_alert_delete.go @@ -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 +} diff --git a/grafana_alerts/client_grafana_alert_get.go b/grafana_alerts/client_grafana_alert_get.go new file mode 100644 index 0000000..f6abbdf --- /dev/null +++ b/grafana_alerts/client_grafana_alert_get.go @@ -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 +} diff --git a/grafana_alerts/client_grafana_alert_list.go b/grafana_alerts/client_grafana_alert_list.go new file mode 100644 index 0000000..d20d855 --- /dev/null +++ b/grafana_alerts/client_grafana_alert_list.go @@ -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 +} diff --git a/grafana_alerts/client_grafana_alert_update.go b/grafana_alerts/client_grafana_alert_update.go new file mode 100644 index 0000000..6e50834 --- /dev/null +++ b/grafana_alerts/client_grafana_alert_update.go @@ -0,0 +1,41 @@ +package grafana_alerts + +import ( + "encoding/json" + "fmt" + logzio_client "github.com/logzio/logzio_terraform_client" + "net/http" +) + +const ( + updateGrafanaAlertServiceUrl = grafanaAlertServiceEndpoint + "/%s" + updateGrafanaAlertServiceMethod = http.MethodPut + updateGrafanaAlertServiceSuccess = http.StatusOK + updateGrafanaAlertServiceNotFound = http.StatusNotFound +) + +func (c *GrafanaAlertClient) UpdateGrafanaAlertRule(payload GrafanaAlertRule) error { + err := validateGrafanaAlertRuleCreateUpdate(payload, true) + if err != nil { + return err + } + + updateGrafanaAlertRuleJson, err := json.Marshal(payload) + if err != nil { + return err + } + + _, err = logzio_client.CallLogzioApi(logzio_client.LogzioApiCallDetails{ + ApiToken: c.ApiToken, + HttpMethod: updateGrafanaAlertServiceMethod, + Url: fmt.Sprintf(updateGrafanaAlertServiceUrl, c.BaseUrl, payload.Uid), + Body: updateGrafanaAlertRuleJson, + SuccessCodes: []int{updateGrafanaAlertServiceSuccess}, + NotFoundCode: updateGrafanaAlertServiceNotFound, + ResourceId: payload.Uid, + ApiAction: operationUpdateGrafanaAlert, + ResourceName: grafanaAlertResourceName, + }) + + return err +} diff --git a/grafana_alerts/grafana_alert_create_integration_test.go b/grafana_alerts/grafana_alert_create_integration_test.go new file mode 100644 index 0000000..3e85436 --- /dev/null +++ b/grafana_alerts/grafana_alert_create_integration_test.go @@ -0,0 +1,104 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/logzio/logzio_terraform_client/test_utils" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestIntegrationGrafanaAlert_CreateGrafanaAlert(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.Title = fmt.Sprintf("%s_%s", createGrafanaAlert.Title, "create") + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + if assert.NoError(t, err) && assert.NotNil(t, grafanaAlert) { + time.Sleep(4 * time.Second) + defer underTest.DeleteGrafanaAlertRule(grafanaAlert.Uid) + assert.NotZero(t, grafanaAlert.Uid) + assert.Equal(t, createGrafanaAlert.Title, grafanaAlert.Title) + } + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoTitle(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.Title = "" + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoData(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.Data = nil + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoFolderUid(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.FolderUID = "" + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoFor(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.For = 0 + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoOrgId(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.OrgID = 0 + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} + +func TestIntegrationGrafanaAlert_CreateGrafanaAlertNoOrgRuleGroup(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.RuleGroup = "" + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} diff --git a/grafana_alerts/grafana_alert_create_test.go b/grafana_alerts/grafana_alert_create_test.go new file mode 100644 index 0000000..5a92f97 --- /dev/null +++ b/grafana_alerts/grafana_alert_create_test.go @@ -0,0 +1,63 @@ +package grafana_alerts_test + +import ( + "encoding/json" + "fmt" + "github.com/logzio/logzio_terraform_client/grafana_alerts" + "github.com/stretchr/testify/assert" + "io" + "net/http" + "testing" +) + +func TestGrafanaAlert_CreateGrafanaAlert(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + defer teardown() + + if assert.NoError(t, err) { + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + jsonBytes, _ := io.ReadAll(r.Body) + var target grafana_alerts.GrafanaAlertRule + err = json.Unmarshal(jsonBytes, &target) + assert.NoError(t, err) + assert.NotNil(t, target) + assert.NotEmpty(t, target.Title) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, fixture("create_grafana_alert_res.json")) + }) + + createGrafanaAlert := getGrafanaAlertRuleObject() + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.NoError(t, err) + assert.NotNil(t, grafanaAlert) + assert.Equal(t, int64(123456), grafanaAlert.Id) + assert.Equal(t, "some_uid", grafanaAlert.Uid) + assert.Equal(t, "folder_uid", grafanaAlert.FolderUID) + assert.Equal(t, createGrafanaAlert.RuleGroup, grafanaAlert.RuleGroup) + } +} + +func TestGrafanaAlert_CreateGrafanaAlertInternalServerError(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + defer teardown() + + if assert.NoError(t, err) { + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + jsonBytes, _ := io.ReadAll(r.Body) + var target grafana_alerts.GrafanaAlertRule + err = json.Unmarshal(jsonBytes, &target) + assert.NoError(t, err) + assert.NotNil(t, target) + assert.NotEmpty(t, target.Title) + w.WriteHeader(http.StatusInternalServerError) + }) + + createGrafanaAlert := getGrafanaAlertRuleObject() + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + assert.Error(t, err) + assert.Nil(t, grafanaAlert) + } +} diff --git a/grafana_alerts/grafana_alert_delete_integration_test.go b/grafana_alerts/grafana_alert_delete_integration_test.go new file mode 100644 index 0000000..38c693d --- /dev/null +++ b/grafana_alerts/grafana_alert_delete_integration_test.go @@ -0,0 +1,35 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/logzio/logzio_terraform_client/test_utils" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestIntegrationGrafanaAlert_DeleteGrafanaAlert(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.Title = fmt.Sprintf("%s_%s", createGrafanaAlert.Title, "delete") + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + if assert.NoError(t, err) && assert.NotNil(t, grafanaAlert) && assert.NotEmpty(t, grafanaAlert.Uid) { + time.Sleep(2 * time.Second) + defer func() { + err = underTest.DeleteGrafanaAlertRule(grafanaAlert.Uid) + assert.NoError(t, err) + }() + } + } +} + +func TestIntegrationGrafanaAlert_DeleteGrafanaAlertEmptyUid(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + if assert.NoError(t, err) { + err = underTest.DeleteGrafanaAlertRule("") + assert.Error(t, err) + } +} diff --git a/grafana_alerts/grafana_alert_delete_test.go b/grafana_alerts/grafana_alert_delete_test.go new file mode 100644 index 0000000..6841673 --- /dev/null +++ b/grafana_alerts/grafana_alert_delete_test.go @@ -0,0 +1,25 @@ +package grafana_alerts_test + +import ( + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestGrafanaAlert_DeleteGrafanaAlert(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + defer teardown() + + if assert.NoError(t, err) { + grafanaAlertUid := "delete-me" + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodDelete, r.Method) + assert.Contains(t, r.URL.String(), grafanaAlertUid) + w.WriteHeader(http.StatusNoContent) + }) + + err = underTest.DeleteGrafanaAlertRule(grafanaAlertUid) + assert.NoError(t, err) + } +} diff --git a/grafana_alerts/grafana_alert_get_integration_test.go b/grafana_alerts/grafana_alert_get_integration_test.go new file mode 100644 index 0000000..4846add --- /dev/null +++ b/grafana_alerts/grafana_alert_get_integration_test.go @@ -0,0 +1,52 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/logzio/logzio_terraform_client/test_utils" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestIntegrationGrafanaAlert_GetGrafanaAlert(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + createGrafanaAlert.Title = fmt.Sprintf("%s_%s", createGrafanaAlert.Title, "get") + if assert.NoError(t, err) && assert.NotNil(t, grafanaAlert) && assert.NotEmpty(t, grafanaAlert.Uid) { + defer underTest.DeleteGrafanaAlertRule(grafanaAlert.Uid) + time.Sleep(4 * time.Second) + getAlert, err := underTest.GetGrafanaAlertRule(grafanaAlert.Uid) + assert.NoError(t, err) + assert.NotNil(t, getAlert) + assert.Equal(t, grafanaAlert.Annotations, getAlert.Annotations) + assert.Equal(t, grafanaAlert.Uid, getAlert.Uid) + assert.Equal(t, grafanaAlert.Id, getAlert.Id) + assert.Equal(t, grafanaAlert.Title, getAlert.Title) + assert.Equal(t, grafanaAlert.RuleGroup, getAlert.RuleGroup) + assert.Equal(t, grafanaAlert.FolderUID, getAlert.FolderUID) + assert.Equal(t, grafanaAlert.Data, getAlert.Data) + assert.Equal(t, grafanaAlert.OrgID, getAlert.OrgID) + assert.Equal(t, grafanaAlert.Condition, getAlert.Condition) + assert.Equal(t, grafanaAlert.ExecErrState, getAlert.ExecErrState) + assert.Equal(t, grafanaAlert.For, getAlert.For) + assert.Equal(t, grafanaAlert.Labels, getAlert.Labels) + assert.Equal(t, grafanaAlert.NoDataState, getAlert.NoDataState) + } + } +} + +func TestIntegrationGrafanaAlert_GetGrafanaAlertUidNotExists(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + defer test_utils.TestDoneTimeBuffer() + + if assert.NoError(t, err) { + getAlert, err := underTest.GetGrafanaAlertRule("someUid") + assert.Error(t, err) + assert.Nil(t, getAlert) + assert.Contains(t, err.Error(), "failed with missing grafana alert rule") + } +} diff --git a/grafana_alerts/grafana_alert_get_test.go b/grafana_alerts/grafana_alert_get_test.go new file mode 100644 index 0000000..98a3ce3 --- /dev/null +++ b/grafana_alerts/grafana_alert_get_test.go @@ -0,0 +1,50 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestGrafanaAlert_GetGrafanaAlert(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + alertUid := "some-uid" + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Contains(t, r.URL.String(), alertUid) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, fixture("create_grafana_alert_res.json")) + }) + + alert, err := underTest.GetGrafanaAlertRule(alertUid) + assert.NoError(t, err) + assert.NotNil(t, alert) + assert.NoError(t, err) + assert.NotNil(t, alert) + assert.Equal(t, int64(123456), alert.Id) + assert.Equal(t, "some_uid", alert.Uid) + assert.Equal(t, "folder_uid", alert.FolderUID) +} + +func TestGrafanaAlert_GetGrafanaAlertInternalError(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + alertUid := "some-id" + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + assert.Contains(t, r.URL.String(), alertUid) + w.WriteHeader(http.StatusInternalServerError) + }) + + alert, err := underTest.GetGrafanaAlertRule(alertUid) + assert.Error(t, err) + assert.Nil(t, alert) +} diff --git a/grafana_alerts/grafana_alert_list_integration_test.go b/grafana_alerts/grafana_alert_list_integration_test.go new file mode 100644 index 0000000..12d1d0a --- /dev/null +++ b/grafana_alerts/grafana_alert_list_integration_test.go @@ -0,0 +1,16 @@ +package grafana_alerts_test + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIntegrationGrafanaAlert_ListGrafanaAlerts(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + + if assert.NoError(t, err) { + alerts, err := underTest.ListGrafanaAlertRules() + assert.NoError(t, err) + assert.NotNil(t, alerts) + } +} diff --git a/grafana_alerts/grafana_alert_list_test.go b/grafana_alerts/grafana_alert_list_test.go new file mode 100644 index 0000000..cf6721d --- /dev/null +++ b/grafana_alerts/grafana_alert_list_test.go @@ -0,0 +1,40 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "net/http" + "testing" +) + +func TestGrafanaAlert_ListGrafanaAlerts(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, fixture("list_grafana_alert_res.json")) + }) + + alerts, err := underTest.ListGrafanaAlertRules() + assert.NoError(t, err) + assert.NotNil(t, alerts) + assert.Equal(t, 1, len(alerts)) +} + +func TestGrafanaAlert_ListGrafanaAlertsInternalServerError(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(http.StatusInternalServerError) + }) + + alerts, err := underTest.ListGrafanaAlertRules() + assert.Error(t, err) + assert.Nil(t, alerts) +} diff --git a/grafana_alerts/grafana_alert_test.go b/grafana_alerts/grafana_alert_test.go new file mode 100644 index 0000000..ecd5184 --- /dev/null +++ b/grafana_alerts/grafana_alert_test.go @@ -0,0 +1,74 @@ +package grafana_alerts_test + +import ( + "encoding/json" + "github.com/logzio/logzio_terraform_client/grafana_alerts" + "github.com/logzio/logzio_terraform_client/test_utils" + "net/http" + "net/http/httptest" + "os" +) + +const ( + envGrafanaFolderUid = "GRAFANA_FOLDER_UID" +) + +var ( + mux *http.ServeMux + server *httptest.Server +) + +func fixture(path string) string { + b, err := os.ReadFile("testdata/fixtures/" + path) + if err != nil { + panic(err) + } + return string(b) +} + +func setupGrafanaAlertRuleTest() (*grafana_alerts.GrafanaAlertClient, error, func()) { + mux = http.NewServeMux() + server = httptest.NewServer(mux) + + apiToken := "SOME_API_TOKEN" + underTest, _ := grafana_alerts.New(apiToken, server.URL) + + return underTest, nil, func() { + server.Close() + } +} + +func setupGrafanaAlertIntegrationTest() (*grafana_alerts.GrafanaAlertClient, error) { + apiToken, err := test_utils.GetApiToken() + if err != nil { + return nil, err + } + + underTest, err := grafana_alerts.New(apiToken, test_utils.GetLogzIoBaseUrl()) + return underTest, err +} + +func getGrafanaAlertRuleObject() grafana_alerts.GrafanaAlertRule { + 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, + }, + } + + return 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), + } +} diff --git a/grafana_alerts/grafana_alert_update_integration_test.go b/grafana_alerts/grafana_alert_update_integration_test.go new file mode 100644 index 0000000..1bdaa25 --- /dev/null +++ b/grafana_alerts/grafana_alert_update_integration_test.go @@ -0,0 +1,41 @@ +package grafana_alerts_test + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestIntegrationGrafanaAlert_UpdateGrafanaAlert(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + + if assert.NoError(t, err) { + createGrafanaAlert := getGrafanaAlertRuleObject() + createGrafanaAlert.Title = fmt.Sprintf("%s_%s", createGrafanaAlert.Title, "update") + grafanaAlert, err := underTest.CreateGrafanaAlertRule(createGrafanaAlert) + if assert.NoError(t, err) && assert.NotNil(t, grafanaAlert) && assert.NotEmpty(t, grafanaAlert.Uid) { + defer underTest.DeleteGrafanaAlertRule(grafanaAlert.Uid) + time.Sleep(time.Second * 2) + grafanaAlert.Title = "changed" + err = underTest.UpdateGrafanaAlertRule(*grafanaAlert) + assert.NoError(t, err) + // verify that the update was made + time.Sleep(time.Second * 4) + getAlert, err := underTest.GetGrafanaAlertRule(grafanaAlert.Uid) + assert.NoError(t, err) + assert.Equal(t, grafanaAlert.Title, getAlert.Title) + } + } +} + +func TestIntegrationGrafanaAlert_UpdateGrafanaAlertIdNotFound(t *testing.T) { + underTest, err := setupGrafanaAlertIntegrationTest() + + if assert.NoError(t, err) { + request := getGrafanaAlertRuleObject() + request.Uid = "not-exist" + err = underTest.UpdateGrafanaAlertRule(request) + assert.Error(t, err) + } +} diff --git a/grafana_alerts/grafana_alert_update_test.go b/grafana_alerts/grafana_alert_update_test.go new file mode 100644 index 0000000..398bf15 --- /dev/null +++ b/grafana_alerts/grafana_alert_update_test.go @@ -0,0 +1,61 @@ +package grafana_alerts_test + +import ( + "encoding/json" + "github.com/logzio/logzio_terraform_client/grafana_alerts" + "github.com/stretchr/testify/assert" + "io" + "net/http" + "testing" +) + +func TestGrafanaAlert_UpdateGrafanaAlert(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + alertUid := "some-uid" + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + assert.Contains(t, r.URL.String(), alertUid) + jsonBytes, _ := io.ReadAll(r.Body) + var target grafana_alerts.GrafanaAlertRule + err = json.Unmarshal(jsonBytes, &target) + assert.NoError(t, err) + assert.NotNil(t, target) + assert.NotEmpty(t, target.Title) + assert.Equal(t, alertUid, target.Uid) + }) + + updateAlert := getGrafanaAlertRuleObject() + updateAlert.Uid = alertUid + err = underTest.UpdateGrafanaAlertRule(updateAlert) + assert.NoError(t, err) +} + +func TestGrafanaAlert_UpdateGrafanaAlertInternalServerError(t *testing.T) { + underTest, err, teardown := setupGrafanaAlertRuleTest() + assert.NoError(t, err) + defer teardown() + + alertUid := "client_test" + + mux.HandleFunc("/v1/grafana/api/v1/provisioning/alert-rules/", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPut, r.Method) + assert.Contains(t, r.URL.String(), alertUid) + jsonBytes, _ := io.ReadAll(r.Body) + var target grafana_alerts.GrafanaAlertRule + err = json.Unmarshal(jsonBytes, &target) + assert.NoError(t, err) + assert.NotNil(t, target) + assert.NotEmpty(t, target.Title) + assert.Equal(t, alertUid, target.Uid) + w.WriteHeader(http.StatusInternalServerError) + }) + + updateAlert := getGrafanaAlertRuleObject() + updateAlert.Uid = alertUid + err = underTest.UpdateGrafanaAlertRule(updateAlert) + assert.Error(t, err) +} diff --git a/grafana_alerts/testdata/fixtures/create_grafana_alert_res.json b/grafana_alerts/testdata/fixtures/create_grafana_alert_res.json new file mode 100644 index 0000000..38644c3 --- /dev/null +++ b/grafana_alerts/testdata/fixtures/create_grafana_alert_res.json @@ -0,0 +1,60 @@ +{ + "id": 123456, + "uid": "some_uid", + "folderUID": "folder_uid", + "ruleGroup": "rule_group_1", + "title": "test_alert", + "condition": "A", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 0, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "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" + } + } + ], + "updated": "2023-09-20T09:47:10Z", + "noDataState": "OK", + "execErrState": "OK", + "for": 3, + "annotations": { + "key_test": "value_test" + } +} diff --git a/grafana_alerts/testdata/fixtures/list_grafana_alert_res.json b/grafana_alerts/testdata/fixtures/list_grafana_alert_res.json new file mode 100644 index 0000000..5c58bd2 --- /dev/null +++ b/grafana_alerts/testdata/fixtures/list_grafana_alert_res.json @@ -0,0 +1,62 @@ +[ + { + "id": 123456, + "uid": "some_uid", + "folderUID": "folder_uid", + "ruleGroup": "rule_group_1", + "title": "test_alert", + "condition": "A", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 0, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "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" + } + } + ], + "updated": "2023-09-20T09:47:10Z", + "noDataState": "OK", + "execErrState": "OK", + "for": 3, + "annotations": { + "key_test": "value_test" + } + } +] \ No newline at end of file diff --git a/grafana_folders/grafana_folder_list_integration_test.go b/grafana_folders/grafana_folder_list_integration_test.go index 6479c78..048c154 100644 --- a/grafana_folders/grafana_folder_list_integration_test.go +++ b/grafana_folders/grafana_folder_list_integration_test.go @@ -9,8 +9,8 @@ func TestIntegrationGrafanaFolder_ListGrafanaFolders(t *testing.T) { underTest, err := setupGrafanaFolderIntegrationTest() if assert.NoError(t, err) { - subAccounts, err := underTest.ListGrafanaFolders() + folders, err := underTest.ListGrafanaFolders() assert.NoError(t, err) - assert.NotNil(t, subAccounts) + assert.NotNil(t, folders) } } diff --git a/utils.go b/utils.go index 04d9cd3..e5fbff9 100644 --- a/utils.go +++ b/utils.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/avast/retry-go" "github.com/logzio/logzio_terraform_client/client" + "io" "io/ioutil" "net/http" "strings" @@ -101,7 +102,7 @@ func CallLogzioApi(logzioCall LogzioApiCallDetails) ([]byte, error) { return err } - jsonBytes, _ = ioutil.ReadAll(resp.Body) + jsonBytes, _ = io.ReadAll(resp.Body) if !CheckValidStatus(resp, logzioCall.SuccessCodes) { if resp.StatusCode == logzioCall.NotFoundCode { return fmt.Errorf("API call %s failed with missing %s %d, data: %s",