Skip to content

Commit

Permalink
Config Generation: Fix oncall (#1742)
Browse files Browse the repository at this point in the history
Currently, we have errors with two oncall resources:
- Schedule: `web` type schedules are fetched but they can't be managed with TF
- Escalation: Mutually exclusive attributes are being set. With this change, all `nil` attributes won't be set in the TF state (rather than a zero value)
  • Loading branch information
julienduchesne authored Aug 2, 2024
1 parent f12a63c commit cf03c87
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 50 deletions.
40 changes: 30 additions & 10 deletions internal/resources/oncall/resource_escalation.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,16 +363,36 @@ func resourceEscalationRead(ctx context.Context, d *schema.ResourceData, client
d.Set("escalation_chain_id", escalation.EscalationChainId)
d.Set("position", escalation.Position)
d.Set("type", escalation.Type)
d.Set("duration", escalation.Duration)
d.Set("notify_on_call_from_schedule", escalation.NotifyOnCallFromSchedule)
d.Set("persons_to_notify", escalation.PersonsToNotify)
d.Set("persons_to_notify_next_each_time", escalation.PersonsToNotifyEachTime)
d.Set("notify_to_team_members", escalation.TeamToNotify)
d.Set("group_to_notify", escalation.GroupToNotify)
d.Set("action_to_trigger", escalation.ActionToTrigger)
d.Set("important", escalation.Important)
d.Set("notify_if_time_from", escalation.NotifyIfTimeFrom)
d.Set("notify_if_time_to", escalation.NotifyIfTimeTo)
if escalation.Duration != nil {
d.Set("duration", escalation.Duration)
}
if escalation.NotifyOnCallFromSchedule != nil {
d.Set("notify_on_call_from_schedule", escalation.NotifyOnCallFromSchedule)
}
if escalation.PersonsToNotify != nil {
d.Set("persons_to_notify", escalation.PersonsToNotify)
}
if escalation.PersonsToNotifyEachTime != nil {
d.Set("persons_to_notify_next_each_time", escalation.PersonsToNotifyEachTime)
}
if escalation.TeamToNotify != nil {
d.Set("notify_to_team_members", escalation.TeamToNotify)
}
if escalation.GroupToNotify != nil {
d.Set("group_to_notify", escalation.GroupToNotify)
}
if escalation.ActionToTrigger != nil {
d.Set("action_to_trigger", escalation.ActionToTrigger)
}
if escalation.Important != nil {
d.Set("important", escalation.Important)
}
if escalation.NotifyIfTimeFrom != nil {
d.Set("notify_if_time_from", escalation.NotifyIfTimeFrom)
}
if escalation.NotifyIfTimeTo != nil {
d.Set("notify_if_time_to", escalation.NotifyIfTimeTo)
}

return nil
}
Expand Down
15 changes: 15 additions & 0 deletions internal/resources/oncall/resource_escalation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ func TestAccOnCallEscalation_basic(t *testing.T) {
resource.TestCheckResourceAttrSet("grafana_oncall_escalation.test-acc-escalation-policy-team", "notify_to_team_members"),
),
},
{
ImportState: true,
ResourceName: "grafana_oncall_escalation.test-acc-escalation",
ImportStateVerify: true,
},
{
ImportState: true,
ResourceName: "grafana_oncall_escalation.test-acc-escalation-repeat",
ImportStateVerify: true,
},
{
ImportState: true,
ResourceName: "grafana_oncall_escalation.test-acc-escalation-policy-team",
ImportStateVerify: true,
},
},
})
}
Expand Down
4 changes: 4 additions & 0 deletions internal/resources/oncall/resource_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package oncall
import (
"context"
"net/http"
"slices"
"strings"

onCallAPI "github.com/grafana/amixr-api-go-client"
Expand Down Expand Up @@ -114,6 +115,9 @@ func listSchedules(client *onCallAPI.Client, listOptions onCallAPI.ListOptions)
return nil, nil, err
}
for _, i := range resp.Schedules {
if !slices.Contains(scheduleTypeOptions, i.Type) {
continue
}
ids = append(ids, i.ID)
}
return ids, resp.Next, nil
Expand Down
15 changes: 15 additions & 0 deletions internal/resources/oncall/resource_schedule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,35 @@ func TestAccOnCallSchedule_basic(t *testing.T) {
resource.TestCheckResourceAttr("grafana_oncall_schedule.test-acc-schedule", "enable_web_overrides", "false"),
),
},
{
ImportState: true,
ResourceName: "grafana_oncall_schedule.test-acc-schedule",
ImportStateVerify: true,
},
{
Config: testAccOnCallScheduleConfigOverrides(scheduleName, true),
Check: resource.ComposeTestCheckFunc(
testAccCheckOnCallScheduleResourceExists("grafana_oncall_schedule.test-acc-schedule"),
resource.TestCheckResourceAttr("grafana_oncall_schedule.test-acc-schedule", "enable_web_overrides", "true"),
),
},
{
ImportState: true,
ResourceName: "grafana_oncall_schedule.test-acc-schedule",
ImportStateVerify: true,
},
{
Config: testAccOnCallScheduleConfigOverrides(scheduleName, false),
Check: resource.ComposeTestCheckFunc(
testAccCheckOnCallScheduleResourceExists("grafana_oncall_schedule.test-acc-schedule"),
resource.TestCheckResourceAttr("grafana_oncall_schedule.test-acc-schedule", "enable_web_overrides", "false"),
),
},
{
ImportState: true,
ResourceName: "grafana_oncall_schedule.test-acc-schedule",
ImportStateVerify: true,
},
},
})
}
Expand Down
171 changes: 131 additions & 40 deletions pkg/generate/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,54 +330,145 @@ func TestAccGenerate_RestrictedPermissions(t *testing.T) {
tc.Run(t)
}

func TestAccGenerate_CloudInstance(t *testing.T) {
func TestAccGenerate_SMCheck(t *testing.T) {
testutils.CheckCloudInstanceTestsEnabled(t)

// Install Terraform to a temporary directory to avoid reinstalling it for each test case.
installDir := t.TempDir()

randomString := acctest.RandString(10)

var smCheckID string
cases := []generateTestCase{
{
name: "sm-check",
config: testutils.TestAccExampleWithReplace(t, "resources/grafana_synthetic_monitoring_check/http_basic.tf", map[string]string{
`"HTTP Defaults"`: strconv.Quote(randomString),
}),
stateCheck: func(s *terraform.State) error {
checkResource, ok := s.RootModule().Resources["grafana_synthetic_monitoring_check.http"]
if !ok {
return fmt.Errorf("expected resource 'grafana_synthetic_monitoring_check.http' to be present")
}
smCheckID = checkResource.Primary.ID
return nil
},
generateConfig: func(cfg *generate.Config) {
cfg.Grafana = &generate.GrafanaConfig{
URL: os.Getenv("GRAFANA_URL"),
Auth: os.Getenv("GRAFANA_AUTH"),
SMURL: os.Getenv("GRAFANA_SM_URL"),
SMAccessToken: os.Getenv("GRAFANA_SM_ACCESS_TOKEN"),
}
cfg.IncludeResources = []string{"grafana_synthetic_monitoring_check._" + smCheckID}
},
check: func(t *testing.T, tempDir string) {
templateAttrs := map[string]string{
"ID": smCheckID,
"Job": randomString,
}
assertFilesWithTemplating(t, tempDir, "testdata/generate/sm-check", []string{
".terraform",
".terraform.lock.hcl",
}, templateAttrs)
},
tc := generateTestCase{
name: "sm-check",
config: testutils.TestAccExampleWithReplace(t, "resources/grafana_synthetic_monitoring_check/http_basic.tf", map[string]string{
`"HTTP Defaults"`: strconv.Quote(randomString),
}),
stateCheck: func(s *terraform.State) error {
checkResource, ok := s.RootModule().Resources["grafana_synthetic_monitoring_check.http"]
if !ok {
return fmt.Errorf("expected resource 'grafana_synthetic_monitoring_check.http' to be present")
}
smCheckID = checkResource.Primary.ID
return nil
},
generateConfig: func(cfg *generate.Config) {
cfg.Grafana = &generate.GrafanaConfig{
URL: os.Getenv("GRAFANA_URL"),
Auth: os.Getenv("GRAFANA_AUTH"),
SMURL: os.Getenv("GRAFANA_SM_URL"),
SMAccessToken: os.Getenv("GRAFANA_SM_ACCESS_TOKEN"),
}
cfg.IncludeResources = []string{"grafana_synthetic_monitoring_check._" + smCheckID}
},
check: func(t *testing.T, tempDir string) {
templateAttrs := map[string]string{
"ID": smCheckID,
"Job": randomString,
}
assertFilesWithTemplating(t, tempDir, "testdata/generate/sm-check", []string{
".terraform",
".terraform.lock.hcl",
}, templateAttrs)
},
}

for _, tc := range cases {
tc.tfInstallDir = installDir
tc.Run(t)
tc.Run(t)
}

func TestAccGenerate_OnCall(t *testing.T) {
testutils.CheckCloudInstanceTestsEnabled(t)

randomString := acctest.RandString(10)

tfConfig := fmt.Sprintf(`
resource "grafana_oncall_integration" "test" {
name = "%[1]s"
type = "grafana"
default_route {}
}
resource "grafana_oncall_escalation_chain" "test"{
name = "%[1]s"
}
resource "grafana_oncall_escalation" "test" {
escalation_chain_id = grafana_oncall_escalation_chain.test.id
type = "wait"
duration = "300"
position = 0
}
resource "grafana_oncall_schedule" "test" {
name = "%[1]s"
type = "calendar"
time_zone = "America/New_York"
}
`, randomString)

var (
oncallIntegrationID string
oncallEscalationChainID string
oncallEscalationID string
oncallScheduleID string
)
tc := generateTestCase{
name: "oncall",
config: tfConfig,
generateConfig: func(cfg *generate.Config) {
cfg.Grafana = &generate.GrafanaConfig{
URL: os.Getenv("GRAFANA_URL"),
Auth: os.Getenv("GRAFANA_AUTH"),
OnCallURL: "https://oncall-prod-us-central-0.grafana.net/oncall",
OnCallAccessToken: os.Getenv("GRAFANA_ONCALL_ACCESS_TOKEN"),
}
cfg.IncludeResources = []string{
"grafana_oncall_integration._" + oncallIntegrationID,
"grafana_oncall_escalation_chain._" + oncallEscalationChainID,
"grafana_oncall_escalation._" + oncallEscalationID,
"grafana_oncall_schedule._" + oncallScheduleID,
}
},
stateCheck: func(s *terraform.State) error {
integrationResource, ok := s.RootModule().Resources["grafana_oncall_integration.test"]
if !ok {
return fmt.Errorf("expected resource 'grafana_oncall_integration.test' to be present")
}
oncallIntegrationID = integrationResource.Primary.ID

chainResource, ok := s.RootModule().Resources["grafana_oncall_escalation_chain.test"]
if !ok {
return fmt.Errorf("expected resource 'grafana_oncall_escalation_chain.test' to be present")
}
oncallEscalationChainID = chainResource.Primary.ID

escalationResource, ok := s.RootModule().Resources["grafana_oncall_escalation.test"]
if !ok {
return fmt.Errorf("expected resource 'grafana_oncall_escalation.test' to be present")
}
oncallEscalationID = escalationResource.Primary.ID

scheduleResource, ok := s.RootModule().Resources["grafana_oncall_schedule.test"]
if !ok {
return fmt.Errorf("expected resource 'grafana_oncall_schedule.test' to be present")
}
oncallScheduleID = scheduleResource.Primary.ID

return nil
},
check: func(t *testing.T, tempDir string) {
templateAttrs := map[string]string{
"Name": randomString,
"IntegrationID": oncallIntegrationID,
"EscalationChainID": oncallEscalationChainID,
"EscalationID": oncallEscalationID,
"ScheduleID": oncallScheduleID,
}
assertFilesWithTemplating(t, tempDir, "testdata/generate/oncall-resources", []string{
".terraform",
".terraform.lock.hcl",
}, templateAttrs)
},
}

tc.Run(t)
}

// assertFiles checks that all files in the "expectedFilesDir" directory match the files in the "gotFilesDir" directory.
Expand Down
19 changes: 19 additions & 0 deletions pkg/generate/testdata/generate/oncall-resources/imports.tf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
to = grafana_oncall_escalation._{{ .EscalationID }}
id = "{{ .EscalationID }}"
}

import {
to = grafana_oncall_escalation_chain._{{ .EscalationChainID }}
id = "{{ .EscalationChainID }}"
}

import {
to = grafana_oncall_integration._{{ .IntegrationID }}
id = "{{ .IntegrationID }}"
}

import {
to = grafana_oncall_schedule._{{ .ScheduleID }}
id = "{{ .ScheduleID }}"
}
15 changes: 15 additions & 0 deletions pkg/generate/testdata/generate/oncall-resources/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
terraform {
required_providers {
grafana = {
source = "grafana/grafana"
version = "999.999.999"
}
}
}

provider "grafana" {
url = "https://tfprovidertests.grafana.net/"
auth = "REDACTED"
oncall_url = "https://oncall-prod-us-central-0.grafana.net/oncall"
oncall_access_token = "REDACTED"
}
29 changes: 29 additions & 0 deletions pkg/generate/testdata/generate/oncall-resources/resources.tf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.

# __generated__ by Terraform from "{{ .EscalationID }}"
resource "grafana_oncall_escalation" "_{{ .EscalationID }}" {
duration = 300
escalation_chain_id = grafana_oncall_escalation_chain._{{ .EscalationChainID }}.id
position = 0
type = "wait"
}

# __generated__ by Terraform from "{{ .EscalationChainID }}"
resource "grafana_oncall_escalation_chain" "_{{ .EscalationChainID }}" {
name = "{{ .Name }}"
}

# __generated__ by Terraform from "{{ .IntegrationID }}"
resource "grafana_oncall_integration" "_{{ .IntegrationID }}" {
name = "{{ .Name }}"
type = "grafana"
}

# __generated__ by Terraform from "{{ .ScheduleID }}"
resource "grafana_oncall_schedule" "_{{ .ScheduleID }}" {
enable_web_overrides = false
name = "{{ .Name }}"
time_zone = "America/New_York"
type = "calendar"
}

0 comments on commit cf03c87

Please sign in to comment.