diff --git a/docs/resources/rule_group.md b/docs/resources/rule_group.md index 1a3e08770..1389c5909 100644 --- a/docs/resources/rule_group.md +++ b/docs/resources/rule_group.md @@ -116,9 +116,12 @@ EOT - `folder_uid` (String) The UID of the folder that the group belongs to. - `interval_seconds` (Number) The interval, in seconds, at which all rules in the group are evaluated. If a group contains many rules, the rules are evaluated sequentially. - `name` (String) The name of the rule group. -- `org_id` (String) The ID of the org to which the group belongs. - `rule` (Block List, Min: 1) The rules within the group. (see [below for nested schema](#nestedblock--rule)) +### Optional + +- `org_id` (String) The Organization ID. If not set, the Org ID defined in the provider block will be used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/internal/resources/grafana/resource_alerting_rule_group.go b/internal/resources/grafana/resource_alerting_rule_group.go index 4f86440d4..146e35137 100644 --- a/internal/resources/grafana/resource_alerting_rule_group.go +++ b/internal/resources/grafana/resource_alerting_rule_group.go @@ -37,6 +37,7 @@ This resource requires Grafana 9.1.0 or later. SchemaVersion: 0, Schema: map[string]*schema.Schema{ + "org_id": orgIDAttribute(), "name": { Type: schema.TypeString, Required: true, @@ -54,12 +55,6 @@ This resource requires Grafana 9.1.0 or later. Required: true, Description: "The interval, in seconds, at which all rules in the group are evaluated. If a group contains many rules, the rules are evaluated sequentially.", }, - "org_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "The ID of the org to which the group belongs.", - }, "rule": { Type: schema.TypeList, Required: true, @@ -192,9 +187,9 @@ This resource requires Grafana 9.1.0 or later. } func readAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*common.Client).GrafanaAPI + client, orgID, idStr := ClientFromExistingOrgResource(meta, data.Id()) - key := UnpackGroupID(data.Id()) + key := UnpackGroupID(idStr) group, err := client.AlertRuleGroup(key.FolderUID, key.Name) if err, shouldReturn := common.CheckReadError("rule group", data, err); shouldReturn { @@ -204,13 +199,13 @@ func readAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta int if err := packRuleGroup(group, data); err != nil { return diag.FromErr(err) } - data.SetId(packGroupID(ruleKeyFromGroup(group))) + data.SetId(MakeOrgResourceID(orgID, packGroupID(ruleKeyFromGroup(group)))) return nil } func createAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*common.Client).GrafanaAPI + client, orgID := ClientFromNewOrgResource(meta, data) group, err := unpackRuleGroup(data) if err != nil { @@ -222,31 +217,29 @@ func createAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta i return diag.FromErr(err) } - data.SetId(packGroupID(key)) + data.SetId(MakeOrgResourceID(orgID, packGroupID(key))) return readAlertRuleGroup(ctx, data, meta) } func updateAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*common.Client).GrafanaAPI + client, _, _ := ClientFromExistingOrgResource(meta, data.Id()) group, err := unpackRuleGroup(data) if err != nil { return diag.FromErr(err) } - key := ruleKeyFromGroup(group) if err = client.SetAlertRuleGroup(group); err != nil { return diag.FromErr(err) } - data.SetId(packGroupID(key)) return readAlertRuleGroup(ctx, data, meta) } func deleteAlertRuleGroup(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*common.Client).GrafanaAPI + client, _, idStr := ClientFromExistingOrgResource(meta, data.Id()) - key := UnpackGroupID(data.Id()) + key := UnpackGroupID(idStr) group, err := client.AlertRuleGroup(key.FolderUID, key.Name) if err != nil { diff --git a/internal/resources/grafana/resource_alerting_rule_group_test.go b/internal/resources/grafana/resource_alerting_rule_group_test.go index 583e01a69..e967c1f3c 100644 --- a/internal/resources/grafana/resource_alerting_rule_group_test.go +++ b/internal/resources/grafana/resource_alerting_rule_group_test.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/terraform-provider-grafana/internal/common" "github.com/grafana/terraform-provider-grafana/internal/resources/grafana" "github.com/grafana/terraform-provider-grafana/internal/testutils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -212,6 +213,54 @@ func TestAccAlertRule_compound(t *testing.T) { }) } +func TestAccAlertRule_inOrg(t *testing.T) { + testutils.CheckOSSTestsEnabled(t, ">=9.1.0") + + var group gapi.RuleGroup + var org gapi.Org + name := acctest.RandString(10) + + resource.ParallelTest(t, resource.TestCase{ + ProviderFactories: testutils.ProviderFactories, + // Implicitly tests deletion. + CheckDestroy: testAlertRuleCheckDestroy(&group), + Steps: []resource.TestStep{ + // Test creation. + { + Config: testAccAlertRuleGroupInOrgConfig(name, 240), + Check: resource.ComposeTestCheckFunc( + testRuleGroupCheckExists("grafana_rule_group.test", &group), + testAccOrganizationCheckExists("grafana_organization.test", &org), + checkResourceIsInOrg("grafana_rule_group.test", "grafana_organization.test"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "name", name), + resource.TestCheckResourceAttr("grafana_rule_group.test", "interval_seconds", "240"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "rule.#", "1"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "rule.0.data.0.model", "{\"hide\":false,\"refId\":\"A\"}"), + ), + }, + // Test update content. + { + Config: testAccAlertRuleGroupInOrgConfig(name, 360), + Check: resource.ComposeTestCheckFunc( + testRuleGroupCheckExists("grafana_rule_group.test", &group), + testAccOrganizationCheckExists("grafana_organization.test", &org), + checkResourceIsInOrg("grafana_rule_group.test", "grafana_organization.test"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "name", name), + resource.TestCheckResourceAttr("grafana_rule_group.test", "interval_seconds", "360"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "rule.#", "1"), + resource.TestCheckResourceAttr("grafana_rule_group.test", "rule.0.data.0.model", "{\"hide\":false,\"refId\":\"A\"}"), + ), + }, + // Test import. + { + ResourceName: "grafana_rule_group.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testRuleGroupCheckExists(rname string, g *gapi.RuleGroup) resource.TestCheckFunc { return func(s *terraform.State) error { resource, ok := s.RootModule().Resources[rname] @@ -223,8 +272,9 @@ func testRuleGroupCheckExists(rname string, g *gapi.RuleGroup) resource.TestChec return fmt.Errorf("resource id not set") } - client := testutils.Provider.Meta().(*common.Client).GrafanaAPI - key := grafana.UnpackGroupID(resource.Primary.ID) + orgID, idStr := grafana.SplitOrgResourceID(resource.Primary.ID) + key := grafana.UnpackGroupID(idStr) + client := testutils.Provider.Meta().(*common.Client).GrafanaAPI.WithOrgID(orgID) grp, err := client.AlertRuleGroup(key.FolderUID, key.Name) if err != nil { return fmt.Errorf("error getting resource: %s", err) @@ -238,6 +288,9 @@ func testRuleGroupCheckExists(rname string, g *gapi.RuleGroup) resource.TestChec func testAlertRuleCheckDestroy(group *gapi.RuleGroup) resource.TestCheckFunc { return func(s *terraform.State) error { client := testutils.Provider.Meta().(*common.Client).GrafanaAPI + if len(group.Rules) > 0 { + client = client.WithOrgID(group.Rules[0].OrgID) + } _, err := client.AlertRuleGroup(group.FolderUID, group.Title) if err == nil && strings.HasPrefix(err.Error(), "status: 404") { return fmt.Errorf("rule group still exists on the server") @@ -245,3 +298,46 @@ func testAlertRuleCheckDestroy(group *gapi.RuleGroup) resource.TestCheckFunc { return nil } } + +func testAccAlertRuleGroupInOrgConfig(name string, interval int) string { + return fmt.Sprintf(` +resource "grafana_organization" "test" { + name = "%[1]s" +} + +resource "grafana_folder" "test" { + org_id = grafana_organization.test.id + title = "%[1]s" +} + +resource "grafana_rule_group" "test" { + org_id = grafana_organization.test.id + name = "%[1]s" + folder_uid = grafana_folder.test.uid + interval_seconds = %[2]d + rule { + name = "My Alert Rule 1" + for = "2m" + condition = "B" + no_data_state = "NoData" + exec_err_state = "Alerting" + 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" + }) + } + } +} +`, name, interval) +}