Add sentry_alert_rule, the Terraform provider's second resource
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.
This commit is contained in:
@@ -24,7 +24,7 @@ func TestProviderSchemaValid(t *testing.T) {
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("provider schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{"endpoint", "token"} {
|
||||
for _, attr := range []string{"endpoint", "alerting_endpoint", "token"} {
|
||||
if _, ok := resp.Schema.Attributes[attr]; !ok {
|
||||
t.Errorf("provider schema missing expected attribute %q", attr)
|
||||
}
|
||||
@@ -64,3 +64,38 @@ func TestDashboardResourceMetadataSetsTypeName(t *testing.T) {
|
||||
t.Fatalf("TypeName = %q, want sentry_dashboard", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertRuleResourceSchemaValid(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
req := resource.SchemaRequest{}
|
||||
resp := &resource.SchemaResponse{}
|
||||
|
||||
newAlertRuleResource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_alert_rule schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{
|
||||
"id", "tenant_id", "name", "description", "query", "query_language",
|
||||
"condition_type", "comparator", "threshold_value", "eval_interval_seconds",
|
||||
"for_minutes", "renotify_interval_minutes", "notification_target_id", "enabled", "created_by",
|
||||
} {
|
||||
if _, ok := resp.Schema.Attributes[attr]; !ok {
|
||||
t.Errorf("sentry_alert_rule schema missing expected attribute %q", attr)
|
||||
}
|
||||
}
|
||||
if !resp.Schema.Attributes["name"].IsRequired() {
|
||||
t.Error(`"name" must be Required`)
|
||||
}
|
||||
if !resp.Schema.Attributes["id"].IsComputed() {
|
||||
t.Error(`"id" must be Computed`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlertRuleResourceMetadataSetsTypeName(t *testing.T) {
|
||||
resp := &resource.MetadataResponse{}
|
||||
newAlertRuleResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "sentry"}, resp)
|
||||
if resp.TypeName != "sentry_alert_rule" {
|
||||
t.Fatalf("TypeName = %q, want sentry_alert_rule", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user