Confirmed with the project owner first: alerting's REST API has no
PUT /rules/{id} at all -- confirmed down to rulestore.Store, which has
Create/List/Get/Delete but no Update method to even wire one to, a real
pre-existing gap in alerting's own API, not something new to this task.
Decided to model sentry_alert_rule as create/destroy only rather than
fake an in-place update via delete-then-recreate inside the resource:
every attribute carries a RequiresReplace plan modifier, so a config
change destroys and recreates the rule, surfacing in the plan output the
real side effect that has (alert_state/delivery-log continuity resets)
instead of hiding it. Adding a real PUT /rules/{id} to alerting would
remove this constraint but is a change to a different module's REST
API, out of scope here.
internal/provider/client.go's new rule type and createRule/getRule/
deleteRule methods talk the exact same JSON contract
sentryctl alerts apply already uses against alerting/internal/httpapi.
GET /rules/{id} actually returns rulestore.RuleWithState (Rule's fields
promoted via anonymous embedding, plus a "state" object) -- the local
rule type has no field for "state" by design, and a new client test
proves that extra key doesn't break parsing.
alerting is a genuinely separate service from api (its own base URL),
so this needed the provider to talk to more than one Sentry service for
the first time: providerData now wraps two *client instances (api,
alerting), with a new alerting_endpoint provider attribute defaulting
the same way sentryctl's --alerting-api/$SENTRYCTL_ALERTING_API_URL
does. dashboardResource's Configure updated to pull .api out of the new
wrapper type instead of a bare *client.
Schema mirrors sentry_dashboard's established pattern: comparator/
threshold_value/renotify_interval_minutes stay nullable (only meaningful
for threshold-condition rules), enabled/for_minutes/query_language are
Optional+Computed with a Terraform-side default matching the API's own
default (true/0/"") rather than leaving the API as sole source of truth
the way dashboard's default_earliest/default_latest deliberately do --
these three have no *pointer* type in the API's Rule struct, so their
"default when omitted" is unconditional, not a real API-side default
that could drift independently.
Verified: client tests are real httptest.Server round trips (same
pattern as sentry_dashboard's). Schema validation needs no Terraform
binary. TestAccAlertRuleResource_basic is a real acceptance test,
skip-gated by TF_ACC same as the dashboard one, including a
plancheck.ExpectResourceAction assertion that a config change actually
plans destroy-then-create -- the concrete, checked version of the
"create/destroy only" design decision, not just a comment. Not run
against a live stack in this environment, same disclosed gap as
everything else Docker-gated in this repo.
90 lines
3.3 KiB
Go
90 lines
3.3 KiB
Go
package provider
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
"github.com/hashicorp/terraform-plugin-testing/plancheck"
|
|
)
|
|
|
|
// Same skip-gated-not-faked posture as TestAccDashboardResource_basic --
|
|
// see that test's doc comment. notification_target_id below is a
|
|
// placeholder: no sentry_notification_target resource exists yet (see
|
|
// the provider README), so a real run of this test would need a
|
|
// pre-existing target id supplied some other way; not a blocker for
|
|
// what this test actually proves, since it has never run against a
|
|
// live stack in this environment regardless.
|
|
func TestAccAlertRuleResource_basic(t *testing.T) {
|
|
resource.Test(t, resource.TestCase{
|
|
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
|
Steps: []resource.TestStep{
|
|
{
|
|
Config: `
|
|
provider "sentry" {
|
|
endpoint = "http://localhost:8080"
|
|
alerting_endpoint = "http://localhost:8081"
|
|
}
|
|
|
|
resource "sentry_alert_rule" "test" {
|
|
name = "Acceptance Test Rule"
|
|
query = "status>=500 | stats count"
|
|
condition_type = "threshold"
|
|
comparator = "gt"
|
|
threshold_value = 5
|
|
eval_interval_seconds = 60
|
|
notification_target_id = "placeholder-target-id"
|
|
}
|
|
`,
|
|
Check: resource.ComposeAggregateTestCheckFunc(
|
|
resource.TestCheckResourceAttr("sentry_alert_rule.test", "name", "Acceptance Test Rule"),
|
|
resource.TestCheckResourceAttr("sentry_alert_rule.test", "comparator", "gt"),
|
|
resource.TestCheckResourceAttr("sentry_alert_rule.test", "threshold_value", "5"),
|
|
resource.TestCheckResourceAttrSet("sentry_alert_rule.test", "id"),
|
|
resource.TestCheckResourceAttrSet("sentry_alert_rule.test", "tenant_id"),
|
|
// Left unset in config -- must come back as the
|
|
// server's own default (true), same "API default,
|
|
// not a duplicated Terraform-side one" reasoning
|
|
// sentry_dashboard's default_earliest/default_latest
|
|
// use.
|
|
resource.TestCheckResourceAttr("sentry_alert_rule.test", "enabled", "true"),
|
|
resource.TestCheckResourceAttr("sentry_alert_rule.test", "for_minutes", "0"),
|
|
),
|
|
},
|
|
{
|
|
// Proves the "create/destroy only" design decision is
|
|
// real, not just documented: alerting has no PUT
|
|
// /rules/{id}, so every attribute is RequiresReplace,
|
|
// and changing one (here, the threshold) must plan a
|
|
// destroy-then-create, never an in-place update.
|
|
Config: `
|
|
provider "sentry" {
|
|
endpoint = "http://localhost:8080"
|
|
alerting_endpoint = "http://localhost:8081"
|
|
}
|
|
|
|
resource "sentry_alert_rule" "test" {
|
|
name = "Acceptance Test Rule"
|
|
query = "status>=500 | stats count"
|
|
condition_type = "threshold"
|
|
comparator = "gt"
|
|
threshold_value = 10
|
|
eval_interval_seconds = 60
|
|
notification_target_id = "placeholder-target-id"
|
|
}
|
|
`,
|
|
ConfigPlanChecks: resource.ConfigPlanChecks{
|
|
PreApply: []plancheck.PlanCheck{
|
|
plancheck.ExpectResourceAction("sentry_alert_rule.test", plancheck.ResourceActionDestroyBeforeCreate),
|
|
},
|
|
},
|
|
Check: resource.TestCheckResourceAttr("sentry_alert_rule.test", "threshold_value", "10"),
|
|
},
|
|
{
|
|
ResourceName: "sentry_alert_rule.test",
|
|
ImportState: true,
|
|
ImportStateVerify: true,
|
|
},
|
|
},
|
|
})
|
|
}
|