diff --git a/internal/resources/grafana/resource_alerting_contact_point.go b/internal/resources/grafana/resource_alerting_contact_point.go index 9febf1161..b4b3e5b8d 100644 --- a/internal/resources/grafana/resource_alerting_contact_point.go +++ b/internal/resources/grafana/resource_alerting_contact_point.go @@ -105,8 +105,8 @@ This resource requires Grafana 9.1.0 or later. ) } -// TODO: Fix lister -// .WithLister(listerFunction(listContactPoints)) +// TODO: Fix contact points lister. Terraform doesn't read any of the sensitive fields (or their container) +// It outputs an empty `email {}` block for example, which is not valid. // func listContactPoints(ctx context.Context, client *goapi.GrafanaHTTPAPI, data *ListerData) ([]string, error) { // orgIDs, err := data.OrgIDs(client) // if err != nil { @@ -117,14 +117,24 @@ This resource requires Grafana 9.1.0 or later. // for _, orgID := range orgIDs { // client = client.Clone().WithOrgID(orgID) -// resp, err := client.Provisioning.GetContactpoints(provisioning.NewGetContactpointsParams()) -// if err != nil { +// // Retry if the API returns 500 because it may be that the alertmanager is not ready in the org yet. +// // The alertmanager is provisioned asynchronously when the org is created. +// if err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { +// resp, err := client.Provisioning.GetContactpoints(provisioning.NewGetContactpointsParams()) +// if err != nil { +// if orgID > 1 && (err.(*runtime.APIError).IsCode(500) || err.(*runtime.APIError).IsCode(403)) { +// return retry.RetryableError(err) +// } +// return retry.NonRetryableError(err) +// } + +// for _, contactPoint := range resp.Payload { +// idMap[MakeOrgResourceID(orgID, contactPoint.Name)] = true +// } +// return nil +// }); err != nil { // return nil, err // } - -// for _, contactPoint := range resp.Payload { -// idMap[MakeOrgResourceID(orgID, contactPoint.Name)] = true -// } // } // var ids []string @@ -138,13 +148,16 @@ This resource requires Grafana 9.1.0 or later. func readContactPoint(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { client, orgID, name := OAPIClientFromExistingOrgResource(meta, data.Id()) - // First, try to fetch the contact point by name. - // If that fails, try to fetch it by the UID of its notifiers. - resp, err := client.Provisioning.GetContactpoints(provisioning.NewGetContactpointsParams().WithName(&name)) + resp, err := client.Provisioning.GetContactpoints(provisioning.NewGetContactpointsParams()) if err != nil { return diag.FromErr(err) } - points := resp.Payload + var points []*models.EmbeddedContactPoint + for _, p := range resp.Payload { + if p.Name == name { + points = append(points, p) + } + } if len(points) == 0 { return common.WarnMissing("contact point", data) } diff --git a/internal/resources/grafana/resource_alerting_notification_policy.go b/internal/resources/grafana/resource_alerting_notification_policy.go index 16866aa6d..c473453d6 100644 --- a/internal/resources/grafana/resource_alerting_notification_policy.go +++ b/internal/resources/grafana/resource_alerting_notification_policy.go @@ -198,6 +198,24 @@ func listNotificationPolicies(ctx context.Context, client *goapi.GrafanaHTTPAPI, var ids []string for _, orgID := range orgIDs { + client = client.Clone().WithOrgID(orgID) + + // Retry if the API returns 500 because it may be that the alertmanager is not ready in the org yet. + // The alertmanager is provisioned asynchronously when the org is created. + if err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + _, err := client.Provisioning.GetPolicyTree() + if err != nil { + if orgID > 1 && (err.(*runtime.APIError).IsCode(500) || err.(*runtime.APIError).IsCode(403)) { + return retry.RetryableError(err) + } + return retry.NonRetryableError(err) + } + + return nil + }); err != nil { + return nil, err + } + ids = append(ids, MakeOrgResourceID(orgID, PolicySingletonID)) } diff --git a/internal/resources/grafana/resource_alerting_rule_group.go b/internal/resources/grafana/resource_alerting_rule_group.go index 76cc7d179..68720b53c 100644 --- a/internal/resources/grafana/resource_alerting_rule_group.go +++ b/internal/resources/grafana/resource_alerting_rule_group.go @@ -9,11 +9,13 @@ import ( "strings" "time" + "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" goapi "github.com/grafana/grafana-openapi-client-go/client" "github.com/grafana/grafana-openapi-client-go/client/provisioning" "github.com/grafana/grafana-openapi-client-go/models" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" @@ -264,13 +266,23 @@ func listRuleGroups(ctx context.Context, client *goapi.GrafanaHTTPAPI, data *Lis for _, orgID := range orgIDs { client = client.Clone().WithOrgID(orgID) - resp, err := client.Provisioning.GetAlertRules() - if err != nil { - return nil, err - } + // Retry if the API returns 500 because it may be that the alertmanager is not ready in the org yet. + // The alertmanager is provisioned asynchronously when the org is created. + if err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { + resp, err := client.Provisioning.GetAlertRules() + if err != nil { + if orgID > 1 && (err.(*runtime.APIError).IsCode(500) || err.(*runtime.APIError).IsCode(403)) { + return retry.RetryableError(err) + } + return retry.NonRetryableError(err) + } - for _, rule := range resp.Payload { - idMap[MakeOrgResourceID(orgID, resourceRuleGroupID.Make(rule.FolderUID, rule.RuleGroup))] = true + for _, rule := range resp.Payload { + idMap[resourceRuleGroupID.Make(orgID, rule.FolderUID, rule.RuleGroup)] = true + } + return nil + }); err != nil { + return nil, err } } diff --git a/pkg/generate/generate_test.go b/pkg/generate/generate_test.go index c4aa89fac..1aedea97d 100644 --- a/pkg/generate/generate_test.go +++ b/pkg/generate/generate_test.go @@ -90,6 +90,20 @@ func TestAccGenerate(t *testing.T) { }) }, }, + { + name: "alerting-in-org", + config: func() string { + content, err := os.ReadFile("testdata/generate/alerting-in-org.tf") + require.NoError(t, err) + return string(content) + }(), + check: func(t *testing.T, tempDir string) { + assertFiles(t, tempDir, "testdata/generate/alerting-in-org", []string{ + ".terraform", + ".terraform.lock.hcl", + }) + }, + }, } for _, tc := range cases { diff --git a/pkg/generate/testdata/generate/alerting-in-org.tf b/pkg/generate/testdata/generate/alerting-in-org.tf new file mode 100644 index 000000000..a2df0076a --- /dev/null +++ b/pkg/generate/testdata/generate/alerting-in-org.tf @@ -0,0 +1,129 @@ +resource "grafana_organization" "test" { + name = "alerting-org" +} + +resource "grafana_contact_point" "test" { + org_id = grafana_organization.test.id + name = "my-contact-point" + email { + addresses = ["hello@example.com"] + } +} + +resource "grafana_notification_policy" "test" { + org_id = grafana_organization.test.id + contact_point = grafana_contact_point.test.name + group_by = ["..."] +} + +resource "grafana_mute_timing" "my_mute_timing" { + org_id = grafana_organization.test.id + name = "My Mute Timing" + + intervals { + times { + start = "04:56" + end = "14:17" + } + weekdays = ["monday", "tuesday:thursday"] + days_of_month = ["1:7", "-1"] + months = ["1:3", "december"] + years = ["2030", "2025:2026"] + location = "America/New_York" + } +} + +resource "grafana_message_template" "my_template" { + org_id = grafana_organization.test.id + name = "My Reusable Template" + template = "{{define \"My Reusable Template\" }}\n template content\n{{ end }}" +} + +resource "grafana_folder" "rule_folder" { + org_id = grafana_organization.test.id + title = "My Alert Rule Folder" + uid = "alert-rule-folder" +} + +resource "grafana_rule_group" "my_alert_rule" { + org_id = grafana_organization.test.id + name = "My Rule Group" + folder_uid = grafana_folder.rule_folder.uid + interval_seconds = 240 + rule { + name = "My Alert Rule 1" + for = "2m" + condition = "B" + no_data_state = "NoData" + exec_err_state = "Alerting" + annotations = { + "a" = "b" + "c" = "d" + } + labels = { + "e" = "f" + "g" = "h" + } + is_paused = false + data { + ref_id = "A" + query_type = "" + relative_time_range { + from = 600 + to = 0 + } + datasource_uid = "PD8C576611E62080A" + model = jsonencode({ + hide = false + intervalMs = 1000 + maxDataPoints = 43200 + refId = "A" + }) + } + data { + ref_id = "B" + query_type = "" + relative_time_range { + from = 0 + to = 0 + } + datasource_uid = "-100" + model = <