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:
@@ -33,8 +33,21 @@ type sentryProvider struct {
|
||||
}
|
||||
|
||||
type sentryProviderModel struct {
|
||||
Endpoint types.String `tfsdk:"endpoint"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
Endpoint types.String `tfsdk:"endpoint"`
|
||||
AlertingEndpoint types.String `tfsdk:"alerting_endpoint"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
}
|
||||
|
||||
// providerData is what Configure hands resources/data sources via
|
||||
// req.ProviderData -- two separate clients, not one, because `alerting`
|
||||
// is a genuinely separate service with its own base URL (its own
|
||||
// REST API, its own port, sometimes its own deployment) -- same split
|
||||
// web/src/lib/api.ts's apiBase/alertingBase and cli/cmd/sentryctl's
|
||||
// --api/--alerting-api already draw, not something invented for this
|
||||
// provider.
|
||||
type providerData struct {
|
||||
api *client
|
||||
alerting *client
|
||||
}
|
||||
|
||||
func (p *sentryProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
|
||||
@@ -44,7 +57,7 @@ func (p *sentryProvider) Metadata(_ context.Context, _ provider.MetadataRequest,
|
||||
|
||||
func (p *sentryProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages Sentry log-aggregation-platform resources. Dashboards only for now -- alert rules, notification targets, and tenant/RBAC resources are real, disclosed future work, not built in this pass; see the provider README.",
|
||||
Description: "Manages Sentry log-aggregation-platform resources. Dashboards and alert rules for now -- notification targets and tenant/RBAC resources are real, disclosed future work, not built in this pass; see the provider README.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"endpoint": schema.StringAttribute{
|
||||
Optional: true,
|
||||
@@ -52,6 +65,14 @@ func (p *sentryProvider) Schema(_ context.Context, _ provider.SchemaRequest, res
|
||||
"$SENTRY_API_ENDPOINT, or \"http://localhost:8080\" if that's unset too -- same " +
|
||||
"default sentryctl's --api/$SENTRYCTL_API_URL uses (cli/cmd/sentryctl/main.go).",
|
||||
},
|
||||
"alerting_endpoint": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "Base URL of the alerting service, e.g. \"http://localhost:8081\" -- a " +
|
||||
"separate service from api, not a path under endpoint above (see " +
|
||||
"/docs/phase-3-alerting-design.md's component boundary). Defaults to " +
|
||||
"$SENTRY_ALERTING_API_ENDPOINT, or \"http://localhost:8081\" if that's unset too -- " +
|
||||
"same default sentryctl's --alerting-api/$SENTRYCTL_ALERTING_API_URL uses.",
|
||||
},
|
||||
"token": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
@@ -84,19 +105,31 @@ func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRe
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
alertingEndpoint := config.AlertingEndpoint.ValueString()
|
||||
if alertingEndpoint == "" {
|
||||
alertingEndpoint = os.Getenv("SENTRY_ALERTING_API_ENDPOINT")
|
||||
}
|
||||
if alertingEndpoint == "" {
|
||||
alertingEndpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
token := config.Token.ValueString()
|
||||
if token == "" {
|
||||
token = os.Getenv("SENTRY_API_TOKEN")
|
||||
}
|
||||
|
||||
c := newClient(endpoint, token)
|
||||
resp.DataSourceData = c
|
||||
resp.ResourceData = c
|
||||
data := &providerData{
|
||||
api: newClient(endpoint, token),
|
||||
alerting: newClient(alertingEndpoint, token),
|
||||
}
|
||||
resp.DataSourceData = data
|
||||
resp.ResourceData = data
|
||||
}
|
||||
|
||||
func (p *sentryProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||
return []func() resource.Resource{
|
||||
newDashboardResource,
|
||||
newAlertRuleResource,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user