-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add resources for creating ML managed alerts
- Loading branch information
1 parent
6beb516
commit 1b94f25
Showing
9 changed files
with
486 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "grafana_machine_learning_alert Resource - terraform-provider-grafana" | ||
subcategory: "Machine Learning" | ||
description: |- | ||
A job defines the queries and model parameters for a machine learning task. | ||
--- | ||
|
||
# grafana_machine_learning_alert (Resource) | ||
|
||
A job defines the queries and model parameters for a machine learning task. | ||
|
||
|
||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `title` (String) The title of the alert. | ||
|
||
### Optional | ||
|
||
- `annotations` (Map of String) Annotations to add to the alert generated in Grafana. | ||
- `anomaly_condition` (String) The condition for when to consider a point as anomalous. | ||
- `for` (String) How long values must be anomalous before firing an alert. | ||
- `job` (String) The forecast this alert belongs to. | ||
- `labels` (Map of String) Labels to add to the alert generated in Grafana. | ||
- `no_data_state` (String) How the alert should be processed when no data is returned by the underlying series | ||
- `outlier` (String) The forecast this alert belongs to. | ||
- `threshold` (String) The threshold of points over the window that need to be anomalous to alert. | ||
- `window` (String) How much time to average values over | ||
|
||
### Read-Only | ||
|
||
- `id` (String) The ID of the alert. | ||
|
||
## Import | ||
|
||
Import is supported using the following syntax: | ||
|
||
```shell | ||
terraform import grafana_machine_learning_alert.name "{{ id }}" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
terraform import grafana_machine_learning_alert.name "{{ id }}" |
36 changes: 36 additions & 0 deletions
36
examples/resources/grafana_machine_learning_alert/job_alert.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
resource "grafana_data_source" "foo" { | ||
type = "prometheus" | ||
name = "prometheus-ds-test" | ||
uid = "prometheus-ds-test-uid" | ||
url = "https://my-instance.com" | ||
basic_auth_enabled = true | ||
basic_auth_username = "username" | ||
|
||
json_data_encoded = jsonencode({ | ||
httpMethod = "POST" | ||
prometheusType = "Mimir" | ||
prometheusVersion = "2.4.0" | ||
}) | ||
|
||
secure_json_data_encoded = jsonencode({ | ||
basicAuthPassword = "password" | ||
}) | ||
} | ||
|
||
resource "grafana_machine_learning_job" "test_job" { | ||
name = "Test Job" | ||
metric = "tf_test_job" | ||
datasource_type = "prometheus" | ||
datasource_uid = grafana_data_source.foo.uid | ||
query_params = { | ||
expr = "grafanacloud_grafana_instance_active_user_count" | ||
} | ||
} | ||
|
||
resource "grafana_machine_learning_alert" "test_job_alert" { | ||
job = grafana_machine_learning_job.test_job | ||
title = "Test Job Alert" | ||
anomaly_condition = "any" | ||
threshould = ">0.8" | ||
window = "15m" | ||
} |
26 changes: 26 additions & 0 deletions
26
examples/resources/grafana_machine_learning_alert/outlier_alert.tf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
resource "grafana_machine_learning_outlier_detector" "my_dbscan_outlier_detector" { | ||
name = "My DBSCAN outlier detector" | ||
description = "My DBSCAN Outlier Detector" | ||
|
||
metric = "tf_test_dbscan_job" | ||
datasource_type = "prometheus" | ||
datasource_uid = "AbCd12345" | ||
query_params = { | ||
expr = "grafanacloud_grafana_instance_active_user_count" | ||
} | ||
interval = 300 | ||
|
||
algorithm { | ||
name = "dbscan" | ||
sensitivity = 0.5 | ||
config { | ||
epsilon = 1.0 | ||
} | ||
} | ||
} | ||
|
||
resource "grafana_machine_learning_alert" "test_job_alert" { | ||
job = grafana_machine_learning_outlier_detector.my_dbscan_outlier_detector | ||
title = "Test Outlier Alert" | ||
window = "1h" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
package machinelearning | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/grafana/machine-learning-go-client/mlapi" | ||
"github.com/grafana/terraform-provider-grafana/v3/internal/common" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" | ||
"github.com/prometheus/common/model" | ||
) | ||
|
||
var resourceAlertID = common.NewResourceID(common.StringIDField("id")) | ||
|
||
func resourceAlert() *common.Resource { | ||
schema := &schema.Resource{ | ||
|
||
Description: ` | ||
A job defines the queries and model parameters for a machine learning task. | ||
`, | ||
|
||
CreateContext: checkClient(resourceAlertCreate), | ||
ReadContext: checkClient(resourceAlertRead), | ||
UpdateContext: checkClient(resourceAlertUpdate), | ||
DeleteContext: checkClient(resourceAlertDelete), | ||
Importer: &schema.ResourceImporter{ | ||
StateContext: schema.ImportStatePassthroughContext, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"job": { | ||
Description: "The forecast this alert belongs to.", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
ExactlyOneOf: []string{"job", "outlier"}, | ||
}, | ||
"outlier": { | ||
Description: "The forecast this alert belongs to.", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
ExactlyOneOf: []string{"job", "outlier"}, | ||
}, | ||
"id": { | ||
Description: "The ID of the alert.", | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"title": { | ||
Description: "The title of the alert.", | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
"anomaly_condition": { | ||
Description: "The condition for when to consider a point as anomalous.", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validation.StringInSlice([]string{"any", "low", "high"}, false), | ||
}, | ||
"for": { | ||
Description: "How long values must be anomalous before firing an alert.", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"threshold": { | ||
Description: "The threshold of points over the window that need to be anomalous to alert.", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"window": { | ||
Description: "How much time to average values over", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"labels": { | ||
Description: "Labels to add to the alert generated in Grafana.", | ||
Type: schema.TypeMap, | ||
Optional: true, | ||
}, | ||
"annotations": { | ||
Description: "Annotations to add to the alert generated in Grafana.", | ||
Type: schema.TypeMap, | ||
Optional: true, | ||
}, | ||
"no_data_state": { | ||
Description: "How the alert should be processed when no data is returned by the underlying series", | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ValidateFunc: validation.StringInSlice([]string{"Alerting", "NoData", "OK"}, false), | ||
}, | ||
}, | ||
} | ||
|
||
return common.NewLegacySDKResource( | ||
common.CategoryMachineLearning, | ||
"grafana_machine_learning_alert", | ||
resourceAlertID, | ||
schema, | ||
) | ||
} | ||
|
||
func resourceAlertCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
c := meta.(*common.Client).MLAPI | ||
alert, err := makeMLAlert(d) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
jobID := d.Get("job").(string) | ||
if jobID != "" { | ||
alert, err = c.NewJobAlert(ctx, jobID, alert) | ||
} else { | ||
outlierID := d.Get("outlier").(string) | ||
alert, err = c.NewOutlierAlert(ctx, outlierID, alert) | ||
} | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
d.SetId(alert.ID) | ||
return resourceAlertRead(ctx, d, meta) | ||
} | ||
|
||
func resourceAlertRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
c := meta.(*common.Client).MLAPI | ||
var ( | ||
alert mlapi.Alert | ||
err error | ||
) | ||
jobID := d.Get("job").(string) | ||
if jobID != "" { | ||
alert, err = c.JobAlert(ctx, jobID, d.Id()) | ||
} else { | ||
outlierID := d.Get("outlier").(string) | ||
alert, err = c.OutlierAlert(ctx, outlierID, d.Id()) | ||
} | ||
|
||
if err, shouldReturn := common.CheckReadError("alert", d, err); shouldReturn { | ||
return err | ||
} | ||
|
||
d.Set("title", alert.Title) | ||
d.Set("anomaly_condition", alert.AnomalyCondition) | ||
d.Set("for", alert.For) | ||
d.Set("threshold", alert.Threshold) | ||
d.Set("window", alert.Window) | ||
d.Set("labels", alert.Labels) | ||
d.Set("annotations", alert.Annotations) | ||
d.Set("no_data_state", alert.NoDataState) | ||
|
||
return nil | ||
} | ||
|
||
func resourceAlertUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
c := meta.(*common.Client).MLAPI | ||
alert, err := makeMLAlert(d) | ||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
jobID := d.Get("job").(string) | ||
if jobID != "" { | ||
_, err = c.UpdateJobAlert(ctx, jobID, alert) | ||
} else { | ||
outlierID := d.Get("outlier").(string) | ||
_, err = c.UpdateOutlierAlert(ctx, outlierID, alert) | ||
} | ||
|
||
if err != nil { | ||
return diag.FromErr(err) | ||
} | ||
return resourceAlertRead(ctx, d, meta) | ||
} | ||
|
||
func resourceAlertDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { | ||
c := meta.(*common.Client).MLAPI | ||
jobID := d.Get("job").(string) | ||
var err error | ||
if jobID != "" { | ||
err = c.DeleteJobAlert(ctx, jobID, d.Id()) | ||
} else { | ||
outlierID := d.Get("outlier").(string) | ||
err = c.DeleteOutlierAlert(ctx, outlierID, d.Id()) | ||
} | ||
return diag.FromErr(err) | ||
} | ||
|
||
func makeMLAlert(d *schema.ResourceData) (mlapi.Alert, error) { | ||
forClause, err := model.ParseDuration(d.Get("for").(string)) | ||
if err != nil { | ||
return mlapi.Alert{}, err | ||
} | ||
window, err := model.ParseDuration(d.Get("window").(string)) | ||
if err != nil { | ||
return mlapi.Alert{}, err | ||
} | ||
labels := map[string]string{} | ||
for k, v := range d.Get("labels").(map[string]interface{}) { | ||
labels[k] = v.(string) | ||
} | ||
annotations := map[string]string{} | ||
for k, v := range d.Get("annotations").(map[string]interface{}) { | ||
annotations[k] = v.(string) | ||
} | ||
return mlapi.Alert{ | ||
ID: d.Id(), | ||
Title: d.Get("title").(string), | ||
AnomalyCondition: mlapi.AnomalyCondition(d.Get("anomaly_condition").(string)), | ||
For: forClause, | ||
Threshold: d.Get("threshold").(string), | ||
Window: window, | ||
Labels: labels, | ||
Annotations: annotations, | ||
NoDataState: mlapi.NoDataState(d.Get("no_data_state").(string)), | ||
}, nil | ||
} |
Oops, something went wrong.