05d166cfa972791e2df8e82aa50a8bad1d92dbff
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
278b24cf67 |
Add sentry_dashboard_panel, resolving panels as their own resource
The open design question named in the last three commits' README --
"a sentry_dashboard_panel resource (or a panels list block on this one)"
-- is resolved: separate resource, matching api/dashboards.Handler's own
shape (a panel is created/updated/deleted independently of its parent
dashboard via its own endpoints, never by rewriting the dashboard's
whole panel list). A nested list block would have forced every panel to
be rewritten on any single panel's change, hiding fine-grained diffs a
separate resource shows naturally -- the more idiomatic Terraform
pattern for independently-lifecycled child resources, and the one that
matches what the API actually does.
Unlike sentry_alert_rule/sentry_notification_target, this resource
supports a real in-place Update -- api/dashboards.Handler actually has a
PUT /dashboards/{id}/panels/{panelId}. Only dashboard_id forces
RequiresReplace: UpdatePanel's SQL matches WHERE id = $panelID AND
dashboard_id = $dashboardID, so changing dashboard_id through the
existing panel's URL wouldn't move it, it would just fail to match --
there's no API operation for "move a panel to a different dashboard."
Panels have no standalone GET endpoint -- only GET /dashboards/{id},
which includes the full panels array. client.go's new getPanel fetches
the parent dashboard and finds the panel by ID within it, returning the
same *apiError{StatusCode: 404} shape a direct GET would whether the
dashboard itself or just the panel within it is gone, so isNotFound
works identically either way. This also means a bare panel ID isn't
enough to import from -- ImportState takes "dashboard_id/panel_id" and
splits on the last "/", the one resource here with a composite import
identifier.
query_language never accepts "sql" for panels specifically -- confirmed
in api/dashboards's own validatePanel ("dashboards only support
pipe-syntax queries, since the dashboard time-range picker is injected
as leading query terms"), a real constraint from the API this client
doesn't re-validate client-side (same "let the API be the one source of
truth for validation" posture the other resources already take), but
documented in the schema so it's not a surprise 400 from Create.
sentry_dashboard_panel gets a matching data source too
(dashboard_id + id both Required, unlike the other three data sources'
single Required id, since getPanel itself needs both).
Verified: client tests are real httptest.Server round trips, including
getPanel finding the right panel within a real dashboard response and
returning a recognizable not-found both when the panel is missing and
when the parent dashboard itself is gone. Schema validation needs no
Terraform binary. TestAccDashboardPanelResource_basic and
TestAccDashboardPanelDataSource_basic are real acceptance tests,
skip-gated by TF_ACC same as the other six -- the resource test proves a
genuine in-place update (a title change, no plancheck needed since
in-place update is the default expectation here, unlike the
create/destroy-only resources). Not run against a live stack in this
environment, same disclosed gap as everything else Docker-gated in this
repo.
|
||
|
|
30ae84cd04 |
Add sentry_notification_target, closing the alert-rule-as-code loop
sentry_alert_rule.notification_target_id could previously only point at
a target created outside Terraform (sentryctl/curl/the web UI) --
without this resource, "manage alert rules as code" was only half true.
Same create/destroy-only shape as sentry_alert_rule and for the same
reason: alerting has no PUT /targets/{id} either, confirmed down to
notifystore.Store (Create/List/Get/Delete, no Update).
client.go's notificationTarget type mirrors notifystore.Target's JSON
shape. headers stays raw JSON bytes end to end -- the client has no
opinion about its shape (neither does alerting's own Target type,
json.RawMessage), and the resource layer round-trips it as a plain
JSON-text string a caller provides via Terraform's jsonencode().
secret is marked Sensitive in the schema, but alerting's own
GET /targets/{id} returns it unredacted (confirmed in
notifystore/store.go -- no redaction at the store or handler layer, an
existing property of alerting's API, not something this provider
introduces). A new client test
(TestGetNotificationTargetReturnsSecretUnredacted) documents that real
behavior so a future change to it would be caught here, not discovered
by surprise. Sensitive keeps the value out of plan/apply console output;
it does not keep it out of Terraform state, the standard caveat for any
sensitive attribute, named explicitly in the schema description and
README rather than left implicit.
Examples updated end to end: sentry_alert_rule's example now creates a
real sentry_notification_target and references its .id, instead of a
placeholder string.
Verified: client tests are real httptest.Server round trips. Schema
validation needs no Terraform binary.
TestAccNotificationTargetResource_basic is a real acceptance test,
skip-gated by TF_ACC same as the other two, including a
plancheck.ExpectResourceAction assertion that a config change actually
plans destroy-then-create, and (since secret really does round-trip
unredacted) a real ImportStateVerify on the secret attribute rather than
one papered over with ImportStateVerifyIgnore. Not run against a live
stack in this environment, same disclosed gap as everything else
Docker-gated in this repo.
|
||
|
|
b98f397221 |
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.
|
||
|
|
49dd050689 |
Start the Terraform provider: sentry_dashboard, the first resource
CLAUDE.md names the Terraform provider a first-class deliverable
alongside sentryctl ("CLI and Terraform provider are first-class, not
afterthoughts"), but no phase before this one had actually built any of
it -- no terraform/ directory existed. This is a first slice, not a
finished provider: one resource, scoped and confirmed with the project
owner before starting (a new pinned external dependency and an
architectural decision not covered in /docs/architecture.md are both
things CLAUDE.md's own "When in doubt" section says to ask about).
New Go module (terraform/, github.com/sentry/sentry/terraform) built on
HashiCorp's terraform-plugin-framework -- the actively-developed
library, not the legacy SDKv2, since there's no existing provider code
to migrate and no reason to start new on the framework HashiCorp itself
steers people away from.
internal/provider/client.go talks the exact same JSON contract
sentryctl's "dashboards apply" and web's Export JSON button already use
against api/dashboards.Handler (POST/GET/PUT/DELETE /dashboards[/{id}]) --
cli/README.md already named this "the seed of a future Terraform
provider: one JSON contract, multiple callers," this is that third
caller, not a new contract invented for Terraform's sake.
sentry_dashboard's schema deliberately leaves default_earliest/
default_latest Optional+Computed with no Terraform-side static default,
even though the API defaults them to "-1h"/"now" when empty -- letting
the API stay the one source of truth for what "unset" means rather than
duplicating that default in two places that could drift. tenant_id is
Computed-only, matching api/dashboards.Handler's own tenantID() doc
comment that a client-supplied value is always overridden server-side.
Panels are not modeled by this resource -- a genuinely separate resource
shape (own lifecycle, own endpoints, own validation needs), scoped out
deliberately, not an oversight. Alert rules, notification targets, and
tenant/RBAC resources are the same: real, disclosed future work, not
attempted in this pass. See terraform/README.md for the full accounting.
Verified: client_test.go runs real HTTP round trips against httptest.
Server (request construction, response parsing, the 404-vs-other-error
distinction Read/Delete need for Terraform's out-of-band-deletion
convention) -- same pattern cli/cmd/sentryctl's own tests already use
against the same api/dashboards endpoints. provider_test.go validates
both schemas are internally well-formed without needing a Terraform
binary. dashboard_resource_test.go's TestAccDashboardResource_basic is a
real acceptance test (terraform-plugin-testing), skip-gated by TF_ACC=1
per that framework's own convention -- even with TF_ACC set it would
still need a live api service (Postgres+ClickHouse) to apply against,
which this environment has no Docker access to bring up, so it has not
actually run here, same disclosed gap as every other live-infra test in
this repo.
|