Rebrand: Sentry -> Cairn OBS
Full rebrand across cosmetic branding, code identifiers, and infrastructure/data-plane naming, using the supplied Cairn OBS logo package. Cosmetic: favicon/logo swap (also closes a stale license-audit finding -- the old favicon was SvelteKit's unreplaced scaffold logo), new centered welcome landing page, larger/legible sidebar logo, page titles, CLAUDE.md/README/docs prose. Code identifiers: Go module path github.com/sentry/sentry -> github.com/cairnobs/cairnobs across all 13 modules and ~91 files (protoc regenerated); Rust crates sentry-agent/sentry-parser/sentry-search -> cairnobs-*; CLI sentryctl -> cairnobsctl; Terraform provider fully renamed (sentry_dashboard etc. -> cairnobs_dashboard, provider type, env vars); every session/auth cookie name; agent config paths and Windows service identity. Deliberately preserved: the gRPC wire protocol's protobuf packages (sentry.logs.v1, sentry.agent.v1) and their Go import directory (proto/sentry/...) -- renaming the wire-level package would break every currently-deployed agent binary (confirmed two real hosts, including mail.inbuxa.com, are actively streaming through this exact contract) until rebuilt and redeployed in lockstep with an ingest cutover. Only the Go module path wrapping the generated code changes. Infrastructure: every docker-compose container name (root and three component-level compose files); the Helm chart (directory, Chart.yaml, named-template helpers, all templates, values.yaml image repos); Kubernetes Operator (CRD group sentry.io -> cairnobs.io, both CRD YAML files, Go identifiers, RBAC markers); the coupled enterprise/tenantcrd package. Caught and fixed real path-coupling bugs along the way: the Helm chart's search/ingest volume mounts and the dev-only-credential detection constant vs. docker-compose.yml's literal values had to move together or a security warning would have silently stopped firing. Data plane: Postgres database sentry_metadata -> cairnobs_metadata and role sentry -> cairnobs; ClickHouse database sentry -> cairnobs; Kafka topic sentry.logs.raw -> cairnobs.logs.raw and its consumer groups. Source-level defaults, docker-compose.yml, and every migrate.sh/ provision script default updated together; already-applied migration files left untouched per this repo's immutable-migration convention. Verified at every layer: all 13 Go modules build/vet/test clean, both Rust workspaces (agent, search) build/clippy/test clean, npm run check/ build clean, docker compose config validates on all four compose files. Live-verified against a real docker stack multiple times through this work, including a final fresh-volume run confirming the actual renamed Postgres database/role, ClickHouse database, and Kafka topic all work end to end with a real login and query, zero console errors.
This commit is contained in:
+49
-49
@@ -1,11 +1,11 @@
|
||||
# terraform-provider-sentry
|
||||
# terraform-provider-cairnobs
|
||||
|
||||
Sentry's Terraform provider -- `CLAUDE.md`'s "Repo conventions" section
|
||||
names this a first-class deliverable alongside `sentryctl`
|
||||
Cairn OBS's Terraform provider -- `CLAUDE.md`'s "Repo conventions" section
|
||||
names this a first-class deliverable alongside `cairnobsctl`
|
||||
("CLI and Terraform provider are first-class, not afterthoughts"), but
|
||||
no phase before this one had actually built any of it. Four resources
|
||||
so far (`sentry_dashboard`, `sentry_dashboard_panel`, `sentry_alert_rule`,
|
||||
`sentry_notification_target`), each paired with a read-only data source
|
||||
so far (`cairnobs_dashboard`, `cairnobs_dashboard_panel`, `cairnobs_alert_rule`,
|
||||
`cairnobs_notification_target`), each paired with a read-only data source
|
||||
of the same name, not a finished provider, built on
|
||||
[HashiCorp's `terraform-plugin-framework`][framework] (the
|
||||
actively-developed library, not the legacy SDKv2 -- there's no existing
|
||||
@@ -21,20 +21,20 @@ framework HashiCorp itself steers new providers away from).
|
||||
of a future Terraform provider: one JSON contract, multiple callers (web
|
||||
export, CLI apply, eventually a provider)." This provider is that third
|
||||
caller -- `internal/provider/client.go` talks the exact same JSON shape
|
||||
`sentryctl dashboards apply` and the web UI's Export JSON button already
|
||||
`cairnobsctl dashboards apply` and the web UI's Export JSON button already
|
||||
use against `api/dashboards.Handler`, not a new contract invented for
|
||||
Terraform's sake. `sentry_dashboard_panel` follows the same contract's
|
||||
Terraform's sake. `cairnobs_dashboard_panel` follows the same contract's
|
||||
panel endpoints, as its own resource rather than a block nested inside
|
||||
`sentry_dashboard` -- see "Panels are their own resource" below.
|
||||
`sentry_alert_rule` follows against `alerting`'s own `POST`/`GET`/
|
||||
`cairnobs_dashboard` -- see "Panels are their own resource" below.
|
||||
`cairnobs_alert_rule` follows against `alerting`'s own `POST`/`GET`/
|
||||
`DELETE /rules[/{id}]` -- the natural next resource, and a real second
|
||||
service (`alerting` is a genuinely separate deployment from `api`, its
|
||||
own base URL), so building it exercised that this provider can talk to
|
||||
more than one Sentry service, not just repeat the dashboards pattern
|
||||
against the same endpoint. `sentry_notification_target` rounds these
|
||||
out -- `sentry_alert_rule.notification_target_id` needs something to
|
||||
more than one Cairn OBS service, not just repeat the dashboards pattern
|
||||
against the same endpoint. `cairnobs_notification_target` rounds these
|
||||
out -- `cairnobs_alert_rule.notification_target_id` needs something to
|
||||
actually point at, and without this resource that id could only ever
|
||||
come from outside Terraform (`sentryctl`, `curl`, the web UI),
|
||||
come from outside Terraform (`cairnobsctl`, `curl`, the web UI),
|
||||
undermining the point of managing rules as code at all.
|
||||
|
||||
## What's built
|
||||
@@ -42,18 +42,18 @@ undermining the point of managing rules as code at all.
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
sentry = {
|
||||
source = "registry.terraform.io/sentry/sentry"
|
||||
cairnobs = {
|
||||
source = "registry.terraform.io/cairnobs/cairnobs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "sentry" {
|
||||
endpoint = "http://localhost:8080" # or $SENTRY_API_ENDPOINT
|
||||
token = var.sentry_api_token # or $SENTRY_API_TOKEN -- optional, only needed once enterprise-auth enforcement is on
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080" # or $CAIRNOBS_API_ENDPOINT
|
||||
token = var.cairnobs_api_token # or $CAIRNOBS_API_TOKEN -- optional, only needed once enterprise-auth enforcement is on
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "example" {
|
||||
resource "cairnobs_dashboard" "example" {
|
||||
name = "Checkout Errors"
|
||||
description = "5xx rate and latency for the checkout service"
|
||||
# default_earliest/default_latest are optional -- left unset, the API
|
||||
@@ -64,68 +64,68 @@ resource "sentry_dashboard" "example" {
|
||||
}
|
||||
```
|
||||
|
||||
Supports `terraform import sentry_dashboard.example <dashboard-id>`.
|
||||
Supports `terraform import cairnobs_dashboard.example <dashboard-id>`.
|
||||
|
||||
**Panels are their own resource, not a nested block.** `api/dashboards.
|
||||
Handler` exposes panel CRUD as its own endpoints (`POST`/`PUT`/
|
||||
`DELETE /dashboards/{id}/panels[/{panelId}]`) -- a panel belongs to
|
||||
exactly one dashboard, has its own lifecycle, and is created/updated/
|
||||
deleted independently, never by rewriting a dashboard's whole panel
|
||||
list, so `sentry_dashboard_panel` follows that shape rather than a
|
||||
list, so `cairnobs_dashboard_panel` follows that shape rather than a
|
||||
nested list block (which would force every panel to be rewritten on any
|
||||
single panel's change, hiding fine-grained diffs a separate resource
|
||||
shows naturally):
|
||||
|
||||
```hcl
|
||||
resource "sentry_dashboard_panel" "example" {
|
||||
dashboard_id = sentry_dashboard.example.id
|
||||
resource "cairnobs_dashboard_panel" "example" {
|
||||
dashboard_id = cairnobs_dashboard.example.id
|
||||
title = "5xx rate over time"
|
||||
query = "status>=500 | timechart count"
|
||||
viz_type = "line" # table, line, bar, single_stat, or top_n
|
||||
# query_language never accepts "sql" for panels -- the API rejects it
|
||||
# outright (dashboards only support pipe-syntax queries, since the
|
||||
# time-range picker is injected as leading query terms). Unlike
|
||||
# sentry_alert_rule/sentry_notification_target, this resource
|
||||
# cairnobs_alert_rule/cairnobs_notification_target, this resource
|
||||
# supports a real in-place update (api/dashboards.Handler has a real
|
||||
# PUT for panels) -- only dashboard_id forces a destroy-and-recreate,
|
||||
# since there's no API operation to move a panel between dashboards.
|
||||
}
|
||||
```
|
||||
|
||||
Supports `terraform import sentry_dashboard_panel.example
|
||||
Supports `terraform import cairnobs_dashboard_panel.example
|
||||
<dashboard-id>/<panel-id>` -- a bare panel ID isn't enough on its own,
|
||||
since `Read` needs the parent `dashboard_id` to know where to look (see
|
||||
`client.go`'s `getPanel` doc comment for why: there's no standalone
|
||||
`GET` for a single panel).
|
||||
|
||||
```hcl
|
||||
provider "sentry" {
|
||||
endpoint = "http://localhost:8080" # or $SENTRY_API_ENDPOINT
|
||||
alerting_endpoint = "http://localhost:8081" # or $SENTRY_ALERTING_API_ENDPOINT -- alerting is a separate service, own base URL
|
||||
token = var.sentry_api_token # or $SENTRY_API_TOKEN -- shared by both services, same as sentryctl's one $SENTRYCTL_TOKEN
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080" # or $CAIRNOBS_API_ENDPOINT
|
||||
alerting_endpoint = "http://localhost:8081" # or $CAIRNOBS_ALERTING_API_ENDPOINT -- alerting is a separate service, own base URL
|
||||
token = var.cairnobs_api_token # or $CAIRNOBS_API_TOKEN -- shared by both services, same as cairnobsctl's one $CAIRNOBSCTL_TOKEN
|
||||
}
|
||||
|
||||
resource "sentry_notification_target" "ops" {
|
||||
resource "cairnobs_notification_target" "ops" {
|
||||
name = "Ops Webhook"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://ops.example.com/hooks/sentry-alerts"
|
||||
webhook_url = "https://ops.example.com/hooks/cairnobs-alerts"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "example" {
|
||||
resource "cairnobs_alert_rule" "example" {
|
||||
name = "Checkout 5xx spike"
|
||||
query = "service=checkout status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
comparator = "gt"
|
||||
threshold_value = 50
|
||||
eval_interval_seconds = 60
|
||||
notification_target_id = sentry_notification_target.ops.id
|
||||
notification_target_id = cairnobs_notification_target.ops.id
|
||||
}
|
||||
```
|
||||
|
||||
Supports `terraform import sentry_alert_rule.example <rule-id>` and
|
||||
`terraform import sentry_notification_target.example <target-id>`.
|
||||
Supports `terraform import cairnobs_alert_rule.example <rule-id>` and
|
||||
`terraform import cairnobs_notification_target.example <target-id>`.
|
||||
|
||||
**`sentry_alert_rule` and `sentry_notification_target` are both
|
||||
**`cairnobs_alert_rule` and `cairnobs_notification_target` are both
|
||||
create/destroy only, not update-in-place.** `alerting`'s REST API has no
|
||||
`PUT /rules/{id}` or `PUT /targets/{id}` at all -- confirmed down to
|
||||
`rulestore.Store`/`notifystore.Store`, both of which have
|
||||
@@ -144,7 +144,7 @@ surfacing it in the plan output the way `RequiresReplace` does. Adding
|
||||
real `PUT` endpoints to `alerting` would remove this constraint, but is
|
||||
a change to a different module's REST API, out of scope for this pass.
|
||||
|
||||
**`sentry_notification_target`'s `secret` attribute is `Sensitive` but
|
||||
**`cairnobs_notification_target`'s `secret` attribute is `Sensitive` but
|
||||
still lands in Terraform state in plaintext.** `alerting`'s own
|
||||
`GET /targets/{id}` returns `secret` unredacted (confirmed in
|
||||
`notifystore/store.go` -- no redaction at the store or handler layer, an
|
||||
@@ -159,32 +159,32 @@ than left implicit.
|
||||
## Data sources
|
||||
|
||||
Each resource above has a matching read-only data source (`data
|
||||
"sentry_dashboard"`, `data "sentry_dashboard_panel"`, `data
|
||||
"sentry_alert_rule"`, `data "sentry_notification_target"`) -- a lookup
|
||||
"cairnobs_dashboard"`, `data "cairnobs_dashboard_panel"`, `data
|
||||
"cairnobs_alert_rule"`, `data "cairnobs_notification_target"`) -- a lookup
|
||||
against the same endpoint the matching resource's own `Read` already
|
||||
uses, nothing new added to `client.go` beyond that. Mechanical and
|
||||
low-risk by design: no new architectural question, no new external
|
||||
service, no new write path -- just reusing the resource's own model/
|
||||
conversion functions (`dashboardModelFromAPI` etc.) against `Required`
|
||||
input instead of a full config. Three of the four take a single
|
||||
`Required` `id`; `sentry_dashboard_panel`'s takes both `dashboard_id`
|
||||
`Required` `id`; `cairnobs_dashboard_panel`'s takes both `dashboard_id`
|
||||
and `id` (both `Required`), matching `getPanel`'s own two-argument shape
|
||||
-- there's no standalone lookup for a panel by ID alone.
|
||||
|
||||
```hcl
|
||||
data "sentry_notification_target" "ops" {
|
||||
data "cairnobs_notification_target" "ops" {
|
||||
id = "target-abc123"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "checkout_5xx" {
|
||||
resource "cairnobs_alert_rule" "checkout_5xx" {
|
||||
# ...
|
||||
notification_target_id = data.sentry_notification_target.ops.id
|
||||
notification_target_id = data.cairnobs_notification_target.ops.id
|
||||
}
|
||||
```
|
||||
|
||||
`sentry_notification_target`'s data source has the same `secret`
|
||||
`cairnobs_notification_target`'s data source has the same `secret`
|
||||
caveat its resource does -- `Sensitive`, but a real value visible in
|
||||
Terraform state; see "`sentry_notification_target`'s `secret`
|
||||
Terraform state; see "`cairnobs_notification_target`'s `secret`
|
||||
attribute" above.
|
||||
|
||||
**Also not built, all real and disclosed, not attempted here:**
|
||||
@@ -197,7 +197,7 @@ attribute" above.
|
||||
`enterprise/` is AGPLv3 same as this provider module; the blocker is
|
||||
purely that the underlying API isn't idempotent-safe yet.)
|
||||
- Publishing to the real Terraform Registry -- `main.go`'s `Address`
|
||||
(`registry.terraform.io/sentry/sentry`) is the address a real
|
||||
(`registry.terraform.io/cairnobs/cairnobs`) is the address a real
|
||||
publication would use, but nothing has actually been published; local
|
||||
use is via `~/.terraformrc`'s `dev_overrides` (see "Building &
|
||||
testing" below) or a local provider mirror.
|
||||
@@ -211,7 +211,7 @@ go test ./...
|
||||
```
|
||||
|
||||
`internal/provider/client_test.go` runs real HTTP round trips against a
|
||||
`httptest.Server` (same pattern `cli/cmd/sentryctl`'s own tests use
|
||||
`httptest.Server` (same pattern `cli/cmd/cairnobsctl`'s own tests use
|
||||
against the same `api/dashboards`/`alerting` endpoints) -- real request
|
||||
construction (method, path, `Authorization` header, JSON body), real
|
||||
response parsing, including the 404-vs-other-error distinction
|
||||
@@ -266,11 +266,11 @@ for real."
|
||||
# local dev override, so `terraform` picks up a locally-built binary
|
||||
# instead of trying to download from the registry (which nothing has
|
||||
# been published to -- see "What's built" above)
|
||||
go build -o terraform-provider-sentry .
|
||||
go build -o terraform-provider-cairnobs .
|
||||
cat <<'EOF' >> ~/.terraformrc
|
||||
provider_installation {
|
||||
dev_overrides {
|
||||
"registry.terraform.io/sentry/sentry" = "/absolute/path/to/this/directory"
|
||||
"registry.terraform.io/cairnobs/cairnobs" = "/absolute/path/to/this/directory"
|
||||
}
|
||||
direct {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
data "cairnobs_alert_rule" "checkout_5xx" {
|
||||
id = "an-existing-rule-id"
|
||||
}
|
||||
|
||||
output "checkout_5xx_notification_target" {
|
||||
value = data.cairnobs_alert_rule.checkout_5xx.notification_target_id
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
data "cairnobs_dashboard" "checkout_errors" {
|
||||
id = "an-existing-dashboard-id"
|
||||
}
|
||||
|
||||
output "checkout_errors_default_earliest" {
|
||||
value = data.cairnobs_dashboard.checkout_errors.default_earliest
|
||||
}
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
# secret comes back unredacted (see the sentry_notification_target
|
||||
# secret comes back unredacted (see the cairnobs_notification_target
|
||||
# resource's schema doc comment) -- this data source's "secret"
|
||||
# attribute is Sensitive for the same reason.
|
||||
data "sentry_notification_target" "ops_webhook" {
|
||||
data "cairnobs_notification_target" "ops_webhook" {
|
||||
id = "an-existing-target-id"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "checkout_5xx" {
|
||||
resource "cairnobs_alert_rule" "checkout_5xx" {
|
||||
name = "Checkout 5xx spike"
|
||||
query = "service=checkout status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
comparator = "gt"
|
||||
threshold_value = 50
|
||||
eval_interval_seconds = 60
|
||||
notification_target_id = data.sentry_notification_target.ops_webhook.id
|
||||
notification_target_id = data.cairnobs_notification_target.ops_webhook.id
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
data "sentry_alert_rule" "checkout_5xx" {
|
||||
id = "an-existing-rule-id"
|
||||
}
|
||||
|
||||
output "checkout_5xx_notification_target" {
|
||||
value = data.sentry_alert_rule.checkout_5xx.notification_target_id
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
data "sentry_dashboard" "checkout_errors" {
|
||||
id = "an-existing-dashboard-id"
|
||||
}
|
||||
|
||||
output "checkout_errors_default_earliest" {
|
||||
value = data.sentry_dashboard.checkout_errors.default_earliest
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
sentry = {
|
||||
source = "registry.terraform.io/sentry/sentry"
|
||||
cairnobs = {
|
||||
source = "registry.terraform.io/cairnobs/cairnobs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "sentry_api_token" {
|
||||
variable "cairnobs_api_token" {
|
||||
type = string
|
||||
default = null
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
provider "sentry" {
|
||||
# Both optional -- default to $SENTRY_API_ENDPOINT/$SENTRY_API_TOKEN,
|
||||
# then http://localhost:8080/no token, matching sentryctl's own
|
||||
# defaults (cli/cmd/sentryctl/main.go). token is only required once a
|
||||
provider "cairnobs" {
|
||||
# Both optional -- default to $CAIRNOBS_API_ENDPOINT/$CAIRNOBS_API_TOKEN,
|
||||
# then http://localhost:8080/no token, matching cairnobsctl's own
|
||||
# defaults (cli/cmd/cairnobsctl/main.go). token is only required once a
|
||||
# deployment turns on enterprise-auth enforcement.
|
||||
endpoint = "http://localhost:8080"
|
||||
token = var.sentry_api_token
|
||||
token = var.cairnobs_api_token
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
terraform import cairnobs_alert_rule.checkout_5xx <rule-id>
|
||||
+4
-4
@@ -1,10 +1,10 @@
|
||||
resource "sentry_notification_target" "ops_webhook" {
|
||||
resource "cairnobs_notification_target" "ops_webhook" {
|
||||
name = "Ops Webhook"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://ops.example.com/hooks/sentry-alerts"
|
||||
webhook_url = "https://ops.example.com/hooks/cairnobs-alerts"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "checkout_5xx" {
|
||||
resource "cairnobs_alert_rule" "checkout_5xx" {
|
||||
name = "Checkout 5xx spike"
|
||||
query = "service=checkout status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
@@ -12,7 +12,7 @@ resource "sentry_alert_rule" "checkout_5xx" {
|
||||
threshold_value = 50
|
||||
eval_interval_seconds = 60
|
||||
for_minutes = 5
|
||||
notification_target_id = sentry_notification_target.ops_webhook.id
|
||||
notification_target_id = cairnobs_notification_target.ops_webhook.id
|
||||
}
|
||||
|
||||
# Create/destroy only -- alerting has no PUT /rules/{id} today, so
|
||||
@@ -0,0 +1 @@
|
||||
terraform import cairnobs_dashboard.checkout_errors <dashboard-id>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
resource "sentry_dashboard" "checkout_errors" {
|
||||
resource "cairnobs_dashboard" "checkout_errors" {
|
||||
name = "Checkout Errors"
|
||||
description = "5xx rate and latency for the checkout service"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# "dashboard_id/panel_id" -- a bare panel id isn't enough to import
|
||||
# from, since Read needs the parent dashboard_id to know where to look
|
||||
# (there's no standalone GET for a single panel).
|
||||
terraform import sentry_dashboard_panel.error_rate <dashboard-id>/<panel-id>
|
||||
terraform import cairnobs_dashboard_panel.error_rate <dashboard-id>/<panel-id>
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
resource "sentry_dashboard" "checkout_errors" {
|
||||
resource "cairnobs_dashboard" "checkout_errors" {
|
||||
name = "Checkout Errors"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard_panel" "error_rate" {
|
||||
dashboard_id = sentry_dashboard.checkout_errors.id
|
||||
resource "cairnobs_dashboard_panel" "error_rate" {
|
||||
dashboard_id = cairnobs_dashboard.checkout_errors.id
|
||||
title = "5xx rate over time"
|
||||
query = "service=checkout status>=500 | timechart count"
|
||||
viz_type = "line"
|
||||
@@ -13,7 +13,7 @@ resource "sentry_dashboard_panel" "error_rate" {
|
||||
height = 4
|
||||
}
|
||||
|
||||
# Unlike sentry_alert_rule/sentry_notification_target, this resource
|
||||
# Unlike cairnobs_alert_rule/cairnobs_notification_target, this resource
|
||||
# supports a real in-place update -- api/dashboards.Handler has a real
|
||||
# PUT /dashboards/{id}/panels/{panelId}. Only dashboard_id forces a
|
||||
# destroy-and-recreate (there's no API operation to move a panel between
|
||||
@@ -0,0 +1 @@
|
||||
terraform import cairnobs_notification_target.ops_webhook <target-id>
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
resource "sentry_notification_target" "ops_webhook" {
|
||||
resource "cairnobs_notification_target" "ops_webhook" {
|
||||
name = "Ops Webhook"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://ops.example.com/hooks/sentry-alerts"
|
||||
webhook_url = "https://ops.example.com/hooks/cairnobs-alerts"
|
||||
|
||||
# Optional. Sensitive -- not printed in plan/apply output, but note
|
||||
# it's still stored in Terraform state in plaintext (alerting's own
|
||||
@@ -1 +0,0 @@
|
||||
terraform import sentry_alert_rule.checkout_5xx <rule-id>
|
||||
@@ -1 +0,0 @@
|
||||
terraform import sentry_dashboard.checkout_errors <dashboard-id>
|
||||
@@ -1 +0,0 @@
|
||||
terraform import sentry_notification_target.ops_webhook <target-id>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
module github.com/sentry/sentry/terraform
|
||||
module github.com/cairnobs/cairnobs/terraform
|
||||
|
||||
go 1.25.8
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ func (d *alertRuleDataSource) Metadata(_ context.Context, req datasource.Metadat
|
||||
|
||||
func (d *alertRuleDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Looks up an existing Sentry alert rule by ID. See the sentry_alert_rule resource for how one is created/managed.",
|
||||
Description: "Looks up an existing Cairn OBS alert rule by ID. See the cairnobs_alert_rule resource for how one is created/managed.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Required: true,
|
||||
|
||||
@@ -15,12 +15,12 @@ func TestAccAlertRuleDataSource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "test" {
|
||||
resource "cairnobs_alert_rule" "test" {
|
||||
name = "Data Source Test Rule"
|
||||
query = "status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
@@ -30,14 +30,14 @@ resource "sentry_alert_rule" "test" {
|
||||
notification_target_id = "placeholder-target-id"
|
||||
}
|
||||
|
||||
data "sentry_alert_rule" "test" {
|
||||
id = sentry_alert_rule.test.id
|
||||
data "cairnobs_alert_rule" "test" {
|
||||
id = cairnobs_alert_rule.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("data.sentry_alert_rule.test", "name", "sentry_alert_rule.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_alert_rule.test", "query", "sentry_alert_rule.test", "query"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_alert_rule.test", "threshold_value", "sentry_alert_rule.test", "threshold_value"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_alert_rule.test", "name", "cairnobs_alert_rule.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_alert_rule.test", "query", "cairnobs_alert_rule.test", "query"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_alert_rule.test", "threshold_value", "cairnobs_alert_rule.test", "threshold_value"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ func newAlertRuleResource() resource.Resource {
|
||||
return &alertRuleResource{}
|
||||
}
|
||||
|
||||
// alertRuleResource implements sentry_alert_rule against
|
||||
// alertRuleResource implements cairnobs_alert_rule against
|
||||
// alerting/internal/httpapi's POST/GET/DELETE /rules[/{id}] endpoints.
|
||||
//
|
||||
// Deliberately create/destroy only, every attribute RequiresReplace:
|
||||
@@ -70,7 +70,7 @@ func (r *alertRuleResource) Metadata(_ context.Context, req resource.MetadataReq
|
||||
func (r *alertRuleResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
replace := []planmodifier.String{stringplanmodifier.RequiresReplace()}
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "A Sentry alert rule. Create/destroy only -- alerting has no update endpoint for " +
|
||||
Description: "A Cairn OBS alert rule. Create/destroy only -- alerting has no update endpoint for " +
|
||||
"rules today (see this resource's Go doc comment), so every attribute below forces a " +
|
||||
"destroy-and-recreate on change, never an in-place update.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
@@ -141,7 +141,7 @@ func (r *alertRuleResource) Schema(_ context.Context, _ resource.SchemaRequest,
|
||||
"notification_target_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: replace,
|
||||
Description: "ID of a sentry_notification_target-managed (or manually created) notification target. The API rejects an empty string. No sentry_notification_target resource exists yet -- see the provider README -- so this has to be a target created some other way (sentryctl, curl, or the web UI) for now.",
|
||||
Description: "ID of a cairnobs_notification_target-managed (or manually created) notification target. The API rejects an empty string. No cairnobs_notification_target resource exists yet -- see the provider README -- so this has to be a target created some other way (cairnobsctl, curl, or the web UI) for now.",
|
||||
},
|
||||
"enabled": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// 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
|
||||
// placeholder: no cairnobs_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
|
||||
@@ -20,12 +20,12 @@ func TestAccAlertRuleResource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "test" {
|
||||
resource "cairnobs_alert_rule" "test" {
|
||||
name = "Acceptance Test Rule"
|
||||
query = "status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
@@ -36,18 +36,18 @@ resource "sentry_alert_rule" "test" {
|
||||
}
|
||||
`,
|
||||
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"),
|
||||
resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "name", "Acceptance Test Rule"),
|
||||
resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "comparator", "gt"),
|
||||
resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "threshold_value", "5"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_alert_rule.test", "id"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_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
|
||||
// cairnobs_dashboard's default_earliest/default_latest
|
||||
// use.
|
||||
resource.TestCheckResourceAttr("sentry_alert_rule.test", "enabled", "true"),
|
||||
resource.TestCheckResourceAttr("sentry_alert_rule.test", "for_minutes", "0"),
|
||||
resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "enabled", "true"),
|
||||
resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "for_minutes", "0"),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -57,12 +57,12 @@ resource "sentry_alert_rule" "test" {
|
||||
// and changing one (here, the threshold) must plan a
|
||||
// destroy-then-create, never an in-place update.
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_alert_rule" "test" {
|
||||
resource "cairnobs_alert_rule" "test" {
|
||||
name = "Acceptance Test Rule"
|
||||
query = "status>=500 | stats count"
|
||||
condition_type = "threshold"
|
||||
@@ -74,13 +74,13 @@ resource "sentry_alert_rule" "test" {
|
||||
`,
|
||||
ConfigPlanChecks: resource.ConfigPlanChecks{
|
||||
PreApply: []plancheck.PlanCheck{
|
||||
plancheck.ExpectResourceAction("sentry_alert_rule.test", plancheck.ResourceActionDestroyBeforeCreate),
|
||||
plancheck.ExpectResourceAction("cairnobs_alert_rule.test", plancheck.ResourceActionDestroyBeforeCreate),
|
||||
},
|
||||
},
|
||||
Check: resource.TestCheckResourceAttr("sentry_alert_rule.test", "threshold_value", "10"),
|
||||
Check: resource.TestCheckResourceAttr("cairnobs_alert_rule.test", "threshold_value", "10"),
|
||||
},
|
||||
{
|
||||
ResourceName: "sentry_alert_rule.test",
|
||||
ResourceName: "cairnobs_alert_rule.test",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
// client is a thin HTTP client against api/dashboards.Handler's REST
|
||||
// endpoints -- deliberately hand-rolled, not generated from an OpenAPI
|
||||
// spec (none exists in this repo yet), the same "boring, well-
|
||||
// understood" posture cli/cmd/sentryctl's own httpclient.go already
|
||||
// understood" posture cli/cmd/cairnobsctl's own httpclient.go already
|
||||
// takes against the same API. Kept separate from that package (not
|
||||
// reused directly) since this one needs typed request/response
|
||||
// marshaling for Terraform's plan/state model, where sentryctl only
|
||||
// marshaling for Terraform's plan/state model, where cairnobsctl only
|
||||
// ever needs to pretty-print whatever JSON comes back.
|
||||
type client struct {
|
||||
baseURL string
|
||||
@@ -40,7 +40,7 @@ type apiError struct {
|
||||
}
|
||||
|
||||
func (e *apiError) Error() string {
|
||||
return fmt.Sprintf("sentry api: request failed with status %d: %s", e.StatusCode, e.Message)
|
||||
return fmt.Sprintf("cairnobs api: request failed with status %d: %s", e.StatusCode, e.Message)
|
||||
}
|
||||
|
||||
func isNotFound(err error) bool {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// TestCreateDashboardSendsExpectedRequest is the same "real
|
||||
// httptest.Server, real HTTP round trip" pattern
|
||||
// cli/cmd/sentryctl's own tests use against the same api/dashboards
|
||||
// cli/cmd/cairnobsctl's own tests use against the same api/dashboards
|
||||
// endpoints -- this client has no fake/mock mode, so its tests exercise
|
||||
// real request construction and real response parsing throughout.
|
||||
func TestCreateDashboardSendsExpectedRequest(t *testing.T) {
|
||||
|
||||
@@ -34,7 +34,7 @@ func (d *dashboardDataSource) Metadata(_ context.Context, req datasource.Metadat
|
||||
|
||||
func (d *dashboardDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Looks up an existing Sentry dashboard by ID. See the sentry_dashboard resource for how one is created/managed.",
|
||||
Description: "Looks up an existing Cairn OBS dashboard by ID. See the cairnobs_dashboard resource for how one is created/managed.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Required: true,
|
||||
|
||||
@@ -14,23 +14,23 @@ func TestAccDashboardDataSource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Data Source Test Dashboard"
|
||||
description = "created by TestAccDashboardDataSource_basic"
|
||||
}
|
||||
|
||||
data "sentry_dashboard" "test" {
|
||||
id = sentry_dashboard.test.id
|
||||
data "cairnobs_dashboard" "test" {
|
||||
id = cairnobs_dashboard.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard.test", "name", "sentry_dashboard.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard.test", "tenant_id", "sentry_dashboard.test", "tenant_id"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard.test", "default_earliest", "sentry_dashboard.test", "default_earliest"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard.test", "name", "cairnobs_dashboard.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard.test", "tenant_id", "cairnobs_dashboard.test", "tenant_id"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard.test", "default_earliest", "cairnobs_dashboard.test", "default_earliest"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,11 +32,11 @@ func (d *dashboardPanelDataSource) Metadata(_ context.Context, req datasource.Me
|
||||
|
||||
func (d *dashboardPanelDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Looks up an existing Sentry dashboard panel by (dashboard_id, id). See the sentry_dashboard_panel resource for how one is created/managed.",
|
||||
Description: "Looks up an existing Cairn OBS dashboard panel by (dashboard_id, id). See the cairnobs_dashboard_panel resource for how one is created/managed.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"dashboard_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "ID of the parent sentry_dashboard.",
|
||||
Description: "ID of the parent cairnobs_dashboard.",
|
||||
},
|
||||
"id": schema.StringAttribute{
|
||||
Required: true,
|
||||
|
||||
@@ -14,30 +14,30 @@ func TestAccDashboardPanelDataSource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Panel Data Source Test Dashboard"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard_panel" "test" {
|
||||
dashboard_id = sentry_dashboard.test.id
|
||||
resource "cairnobs_dashboard_panel" "test" {
|
||||
dashboard_id = cairnobs_dashboard.test.id
|
||||
title = "Errors over time"
|
||||
query = "status>=500 | timechart count"
|
||||
viz_type = "line"
|
||||
}
|
||||
|
||||
data "sentry_dashboard_panel" "test" {
|
||||
dashboard_id = sentry_dashboard.test.id
|
||||
id = sentry_dashboard_panel.test.id
|
||||
data "cairnobs_dashboard_panel" "test" {
|
||||
dashboard_id = cairnobs_dashboard.test.id
|
||||
id = cairnobs_dashboard_panel.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard_panel.test", "title", "sentry_dashboard_panel.test", "title"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard_panel.test", "query", "sentry_dashboard_panel.test", "query"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_dashboard_panel.test", "viz_type", "sentry_dashboard_panel.test", "viz_type"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard_panel.test", "title", "cairnobs_dashboard_panel.test", "title"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard_panel.test", "query", "cairnobs_dashboard_panel.test", "query"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_dashboard_panel.test", "viz_type", "cairnobs_dashboard_panel.test", "viz_type"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,10 +26,10 @@ func newDashboardPanelResource() resource.Resource {
|
||||
return &dashboardPanelResource{}
|
||||
}
|
||||
|
||||
// dashboardPanelResource implements sentry_dashboard_panel against
|
||||
// dashboardPanelResource implements cairnobs_dashboard_panel against
|
||||
// api/dashboards.Handler's POST/PUT/DELETE
|
||||
// /dashboards/{id}/panels[/{panelId}] endpoints -- a genuinely separate
|
||||
// resource from sentry_dashboard (own id, own lifecycle, own endpoints),
|
||||
// resource from cairnobs_dashboard (own id, own lifecycle, own endpoints),
|
||||
// not a nested block on the dashboard resource. That split matches the
|
||||
// API's own shape (a panel is created/updated/deleted independently of
|
||||
// its parent dashboard, never by rewriting the dashboard's whole panel
|
||||
@@ -38,7 +38,7 @@ func newDashboardPanelResource() resource.Resource {
|
||||
// panel to be rewritten on any single panel's change, hiding
|
||||
// fine-grained diffs a separate resource shows naturally.
|
||||
//
|
||||
// Unlike sentry_alert_rule/sentry_notification_target, this resource
|
||||
// Unlike cairnobs_alert_rule/cairnobs_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 a
|
||||
// replace: UpdatePanel's SQL matches WHERE id = $panelID AND
|
||||
@@ -76,7 +76,7 @@ func (r *dashboardPanelResource) Metadata(_ context.Context, req resource.Metada
|
||||
|
||||
func (r *dashboardPanelResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "A panel on a Sentry dashboard, managed independently of the sentry_dashboard it belongs to.",
|
||||
Description: "A panel on a Cairn OBS dashboard, managed independently of the cairnobs_dashboard it belongs to.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
@@ -86,7 +86,7 @@ func (r *dashboardPanelResource) Schema(_ context.Context, _ resource.SchemaRequ
|
||||
"dashboard_id": schema.StringAttribute{
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
|
||||
Description: "ID of the sentry_dashboard this panel belongs to. Forces replacement on change -- there is no API operation to move a panel between dashboards, see this resource's Go doc comment.",
|
||||
Description: "ID of the cairnobs_dashboard this panel belongs to. Forces replacement on change -- there is no API operation to move a panel between dashboards, see this resource's Go doc comment.",
|
||||
},
|
||||
"title": schema.StringAttribute{
|
||||
Optional: true,
|
||||
@@ -101,7 +101,7 @@ func (r *dashboardPanelResource) Schema(_ context.Context, _ resource.SchemaRequ
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: stringdefault.StaticString(""),
|
||||
Description: `"" (auto-detect) or "spl" -- never "sql", the API rejects that for panels specifically (unlike sentry_alert_rule's query_language, which accepts it).`,
|
||||
Description: `"" (auto-detect) or "spl" -- never "sql", the API rejects that for panels specifically (unlike cairnobs_alert_rule's query_language, which accepts it).`,
|
||||
},
|
||||
"viz_type": schema.StringAttribute{
|
||||
Required: true,
|
||||
|
||||
@@ -16,66 +16,66 @@ func TestAccDashboardPanelResource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Panel Acceptance Test Dashboard"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard_panel" "test" {
|
||||
dashboard_id = sentry_dashboard.test.id
|
||||
resource "cairnobs_dashboard_panel" "test" {
|
||||
dashboard_id = cairnobs_dashboard.test.id
|
||||
title = "Errors over time"
|
||||
query = "status>=500 | timechart count"
|
||||
viz_type = "line"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("sentry_dashboard_panel.test", "dashboard_id", "sentry_dashboard.test", "id"),
|
||||
resource.TestCheckResourceAttr("sentry_dashboard_panel.test", "title", "Errors over time"),
|
||||
resource.TestCheckResourceAttr("sentry_dashboard_panel.test", "viz_type", "line"),
|
||||
resource.TestCheckResourceAttrSet("sentry_dashboard_panel.test", "id"),
|
||||
resource.TestCheckResourceAttrPair("cairnobs_dashboard_panel.test", "dashboard_id", "cairnobs_dashboard.test", "id"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard_panel.test", "title", "Errors over time"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard_panel.test", "viz_type", "line"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_dashboard_panel.test", "id"),
|
||||
// Left unset in config -- must come back as the
|
||||
// API's own default ("{}"), same "API default, not
|
||||
// a duplicated Terraform-side one" reasoning
|
||||
// sentry_dashboard's default_earliest/default_latest
|
||||
// cairnobs_dashboard's default_earliest/default_latest
|
||||
// use.
|
||||
resource.TestCheckResourceAttr("sentry_dashboard_panel.test", "viz_config", "{}"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard_panel.test", "viz_config", "{}"),
|
||||
),
|
||||
},
|
||||
{
|
||||
// Update: unlike sentry_alert_rule/sentry_notification_target,
|
||||
// Update: unlike cairnobs_alert_rule/cairnobs_notification_target,
|
||||
// this really is an in-place update -- api/dashboards.Handler
|
||||
// has a real PUT for panels.
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Panel Acceptance Test Dashboard"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard_panel" "test" {
|
||||
dashboard_id = sentry_dashboard.test.id
|
||||
resource "cairnobs_dashboard_panel" "test" {
|
||||
dashboard_id = cairnobs_dashboard.test.id
|
||||
title = "Errors over time (renamed)"
|
||||
query = "status>=500 | timechart count"
|
||||
viz_type = "line"
|
||||
}
|
||||
`,
|
||||
Check: resource.TestCheckResourceAttr("sentry_dashboard_panel.test", "title", "Errors over time (renamed)"),
|
||||
Check: resource.TestCheckResourceAttr("cairnobs_dashboard_panel.test", "title", "Errors over time (renamed)"),
|
||||
},
|
||||
{
|
||||
// "dashboard_id/panel_id" -- see ImportState's doc
|
||||
// comment on splitImportID for why a bare panel ID
|
||||
// isn't enough.
|
||||
ResourceName: "sentry_dashboard_panel.test",
|
||||
ResourceName: "cairnobs_dashboard_panel.test",
|
||||
ImportState: true,
|
||||
ImportStateIdFunc: func(s *tfstate.State) (string, error) {
|
||||
rs, ok := s.RootModule().Resources["sentry_dashboard_panel.test"]
|
||||
rs, ok := s.RootModule().Resources["cairnobs_dashboard_panel.test"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("sentry_dashboard_panel.test not found in state")
|
||||
return "", fmt.Errorf("cairnobs_dashboard_panel.test not found in state")
|
||||
}
|
||||
return rs.Primary.Attributes["dashboard_id"] + "/" + rs.Primary.Attributes["id"], nil
|
||||
},
|
||||
|
||||
@@ -23,9 +23,9 @@ func newDashboardResource() resource.Resource {
|
||||
return &dashboardResource{}
|
||||
}
|
||||
|
||||
// dashboardResource implements sentry_dashboard against
|
||||
// dashboardResource implements cairnobs_dashboard against
|
||||
// api/dashboards.Handler's POST/GET/PUT/DELETE /dashboards[/{id}]
|
||||
// endpoints -- the exact same JSON contract cli/cmd/sentryctl's
|
||||
// endpoints -- the exact same JSON contract cli/cmd/cairnobsctl's
|
||||
// "dashboards apply" and web's Export JSON button already use (see
|
||||
// cli/README.md's "one JSON contract, multiple callers" framing; this
|
||||
// is that third caller). Panels are a separate CRUD surface
|
||||
@@ -54,7 +54,7 @@ func (r *dashboardResource) Metadata(_ context.Context, req resource.MetadataReq
|
||||
|
||||
func (r *dashboardResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "A Sentry dashboard. Panels aren't managed by this resource yet -- see the provider README.",
|
||||
Description: "A Cairn OBS dashboard. Panels aren't managed by this resource yet -- see the provider README.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
@@ -167,7 +167,7 @@ func (r *dashboardResource) Read(ctx context.Context, req resource.ReadRequest,
|
||||
out, err := r.client.getDashboard(ctx, state.ID.ValueString())
|
||||
if err != nil {
|
||||
if isNotFound(err) {
|
||||
// Deleted out-of-band (e.g. via web or sentryctl) --
|
||||
// Deleted out-of-band (e.g. via web or cairnobsctl) --
|
||||
// dropping it from state lets the next plan offer to
|
||||
// recreate it, the standard Terraform convention, rather
|
||||
// than failing every subsequent plan/apply until someone
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// runner -- HashiCorp's standard pattern, one factory reused by every
|
||||
// acceptance test in this package.
|
||||
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
|
||||
"sentry": providerserver.NewProtocol6WithError(New("test")()),
|
||||
"cairnobs": providerserver.NewProtocol6WithError(New("test")()),
|
||||
}
|
||||
|
||||
// The acceptance test below is gated the same way every other live-
|
||||
@@ -32,49 +32,49 @@ func TestAccDashboardResource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Acceptance Test Dashboard"
|
||||
description = "created by TestAccDashboardResource_basic"
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("sentry_dashboard.test", "name", "Acceptance Test Dashboard"),
|
||||
resource.TestCheckResourceAttr("sentry_dashboard.test", "description", "created by TestAccDashboardResource_basic"),
|
||||
resource.TestCheckResourceAttrSet("sentry_dashboard.test", "id"),
|
||||
resource.TestCheckResourceAttrSet("sentry_dashboard.test", "tenant_id"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard.test", "name", "Acceptance Test Dashboard"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard.test", "description", "created by TestAccDashboardResource_basic"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_dashboard.test", "id"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_dashboard.test", "tenant_id"),
|
||||
// Left unset in config -- must come back as the
|
||||
// server's own defaults (store.go: "-1h"/"now"), not
|
||||
// an empty string, proving the Optional+Computed
|
||||
// schema round-trips the server's default rather
|
||||
// than fighting it with a Terraform-side one.
|
||||
resource.TestCheckResourceAttr("sentry_dashboard.test", "default_earliest", "-1h"),
|
||||
resource.TestCheckResourceAttr("sentry_dashboard.test", "default_latest", "now"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard.test", "default_earliest", "-1h"),
|
||||
resource.TestCheckResourceAttr("cairnobs_dashboard.test", "default_latest", "now"),
|
||||
),
|
||||
},
|
||||
{
|
||||
// Update: name change should apply in place, not
|
||||
// replace (no RequiresReplace plan modifier on name).
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
}
|
||||
|
||||
resource "sentry_dashboard" "test" {
|
||||
resource "cairnobs_dashboard" "test" {
|
||||
name = "Renamed Dashboard"
|
||||
description = "created by TestAccDashboardResource_basic"
|
||||
}
|
||||
`,
|
||||
Check: resource.TestCheckResourceAttr("sentry_dashboard.test", "name", "Renamed Dashboard"),
|
||||
Check: resource.TestCheckResourceAttr("cairnobs_dashboard.test", "name", "Renamed Dashboard"),
|
||||
},
|
||||
{
|
||||
// Import: re-reads by ID alone and must match what's in
|
||||
// state, proving Read()'s server round trip agrees with
|
||||
// what Create()/Update() last wrote.
|
||||
ResourceName: "sentry_dashboard.test",
|
||||
ResourceName: "cairnobs_dashboard.test",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
|
||||
@@ -31,7 +31,7 @@ func (d *notificationTargetDataSource) Metadata(_ context.Context, req datasourc
|
||||
|
||||
func (d *notificationTargetDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Looks up an existing Sentry notification target by ID. See the sentry_notification_target resource for how one is created/managed.",
|
||||
Description: "Looks up an existing Cairn OBS notification target by ID. See the cairnobs_notification_target resource for how one is created/managed.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Required: true,
|
||||
@@ -49,7 +49,7 @@ func (d *notificationTargetDataSource) Schema(_ context.Context, _ datasource.Sc
|
||||
Computed: true,
|
||||
Sensitive: true,
|
||||
Description: "alerting's own GET /targets/{id} returns this unredacted (see the " +
|
||||
"sentry_notification_target resource's schema doc comment) -- Sensitive here for the " +
|
||||
"cairnobs_notification_target resource's schema doc comment) -- Sensitive here for the " +
|
||||
"same reason, and the same state-file caveat applies.",
|
||||
},
|
||||
"created_by": schema.StringAttribute{Computed: true},
|
||||
|
||||
@@ -15,29 +15,29 @@ func TestAccNotificationTargetDataSource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_notification_target" "test" {
|
||||
resource "cairnobs_notification_target" "test" {
|
||||
name = "Data Source Test Target"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://example.com/hook"
|
||||
secret = "test-secret"
|
||||
}
|
||||
|
||||
data "sentry_notification_target" "test" {
|
||||
id = sentry_notification_target.test.id
|
||||
data "cairnobs_notification_target" "test" {
|
||||
id = cairnobs_notification_target.test.id
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttrPair("data.sentry_notification_target.test", "name", "sentry_notification_target.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.sentry_notification_target.test", "webhook_url", "sentry_notification_target.test", "webhook_url"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_notification_target.test", "name", "cairnobs_notification_target.test", "name"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_notification_target.test", "webhook_url", "cairnobs_notification_target.test", "webhook_url"),
|
||||
// Real, not papered over -- secret round-trips
|
||||
// unredacted (see the resource test's equivalent
|
||||
// comment).
|
||||
resource.TestCheckResourceAttrPair("data.sentry_notification_target.test", "secret", "sentry_notification_target.test", "secret"),
|
||||
resource.TestCheckResourceAttrPair("data.cairnobs_notification_target.test", "secret", "cairnobs_notification_target.test", "secret"),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ func newNotificationTargetResource() resource.Resource {
|
||||
return ¬ificationTargetResource{}
|
||||
}
|
||||
|
||||
// notificationTargetResource implements sentry_notification_target
|
||||
// notificationTargetResource implements cairnobs_notification_target
|
||||
// against alerting/internal/httpapi's POST/GET/DELETE
|
||||
// /targets[/{id}] endpoints.
|
||||
//
|
||||
@@ -54,14 +54,14 @@ func (r *notificationTargetResource) Metadata(_ context.Context, req resource.Me
|
||||
func (r *notificationTargetResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
replace := []planmodifier.String{stringplanmodifier.RequiresReplace()}
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "A Sentry alert notification target. Create/destroy only -- alerting has no update " +
|
||||
Description: "A Cairn OBS alert notification target. Create/destroy only -- alerting has no update " +
|
||||
"endpoint for targets today (see this resource's Go doc comment), so every attribute below " +
|
||||
"forces a destroy-and-recreate on change, never an in-place update.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
||||
Description: "Server-generated target ID -- reference this from a sentry_alert_rule's notification_target_id.",
|
||||
Description: "Server-generated target ID -- reference this from a cairnobs_alert_rule's notification_target_id.",
|
||||
},
|
||||
"tenant_id": schema.StringAttribute{
|
||||
Computed: true,
|
||||
|
||||
@@ -15,12 +15,12 @@ func TestAccNotificationTargetResource_basic(t *testing.T) {
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_notification_target" "test" {
|
||||
resource "cairnobs_notification_target" "test" {
|
||||
name = "Acceptance Test Target"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://example.com/hook"
|
||||
@@ -28,11 +28,11 @@ resource "sentry_notification_target" "test" {
|
||||
}
|
||||
`,
|
||||
Check: resource.ComposeAggregateTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("sentry_notification_target.test", "name", "Acceptance Test Target"),
|
||||
resource.TestCheckResourceAttr("sentry_notification_target.test", "kind", "webhook"),
|
||||
resource.TestCheckResourceAttr("sentry_notification_target.test", "secret", "test-secret"),
|
||||
resource.TestCheckResourceAttrSet("sentry_notification_target.test", "id"),
|
||||
resource.TestCheckResourceAttrSet("sentry_notification_target.test", "tenant_id"),
|
||||
resource.TestCheckResourceAttr("cairnobs_notification_target.test", "name", "Acceptance Test Target"),
|
||||
resource.TestCheckResourceAttr("cairnobs_notification_target.test", "kind", "webhook"),
|
||||
resource.TestCheckResourceAttr("cairnobs_notification_target.test", "secret", "test-secret"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_notification_target.test", "id"),
|
||||
resource.TestCheckResourceAttrSet("cairnobs_notification_target.test", "tenant_id"),
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -40,12 +40,12 @@ resource "sentry_notification_target" "test" {
|
||||
// TestAccAlertRuleResource_basic's second step --
|
||||
// notifystore.Store has no Update either.
|
||||
Config: `
|
||||
provider "sentry" {
|
||||
provider "cairnobs" {
|
||||
endpoint = "http://localhost:8080"
|
||||
alerting_endpoint = "http://localhost:8081"
|
||||
}
|
||||
|
||||
resource "sentry_notification_target" "test" {
|
||||
resource "cairnobs_notification_target" "test" {
|
||||
name = "Renamed Target"
|
||||
kind = "webhook"
|
||||
webhook_url = "https://example.com/hook"
|
||||
@@ -54,10 +54,10 @@ resource "sentry_notification_target" "test" {
|
||||
`,
|
||||
ConfigPlanChecks: resource.ConfigPlanChecks{
|
||||
PreApply: []plancheck.PlanCheck{
|
||||
plancheck.ExpectResourceAction("sentry_notification_target.test", plancheck.ResourceActionDestroyBeforeCreate),
|
||||
plancheck.ExpectResourceAction("cairnobs_notification_target.test", plancheck.ResourceActionDestroyBeforeCreate),
|
||||
},
|
||||
},
|
||||
Check: resource.TestCheckResourceAttr("sentry_notification_target.test", "name", "Renamed Target"),
|
||||
Check: resource.TestCheckResourceAttr("cairnobs_notification_target.test", "name", "Renamed Target"),
|
||||
},
|
||||
{
|
||||
// No ImportStateVerifyIgnore for "secret" -- GET
|
||||
@@ -65,7 +65,7 @@ resource "sentry_notification_target" "test" {
|
||||
// doc comment and TestGetNotificationTargetReturnsSecretUnredacted),
|
||||
// so import-time equality is a real, meaningful
|
||||
// assertion here, not one this test has to paper over.
|
||||
ResourceName: "sentry_notification_target.test",
|
||||
ResourceName: "cairnobs_notification_target.test",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package provider is Sentry's Terraform provider implementation,
|
||||
// Package provider is Cairn OBS's Terraform provider implementation,
|
||||
// built on HashiCorp's terraform-plugin-framework (not the legacy
|
||||
// SDKv2 -- the framework is the actively-developed, currently-
|
||||
// recommended library for a provider started from scratch, matching
|
||||
@@ -17,22 +17,22 @@ import (
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
var _ provider.Provider = &sentryProvider{}
|
||||
var _ provider.Provider = &cairnobsProvider{}
|
||||
|
||||
// New matches providerserver.Serve's expected constructor shape --
|
||||
// version is threaded through from main.go's -ldflags-injected build
|
||||
// version.
|
||||
func New(version string) func() provider.Provider {
|
||||
return func() provider.Provider {
|
||||
return &sentryProvider{version: version}
|
||||
return &cairnobsProvider{version: version}
|
||||
}
|
||||
}
|
||||
|
||||
type sentryProvider struct {
|
||||
type cairnobsProvider struct {
|
||||
version string
|
||||
}
|
||||
|
||||
type sentryProviderModel struct {
|
||||
type cairnobsProviderModel struct {
|
||||
Endpoint types.String `tfsdk:"endpoint"`
|
||||
AlertingEndpoint types.String `tfsdk:"alerting_endpoint"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
@@ -42,7 +42,7 @@ type sentryProviderModel struct {
|
||||
// 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
|
||||
// web/src/lib/api.ts's apiBase/alertingBase and cli/cmd/cairnobsctl's
|
||||
// --api/--alerting-api already draw, not something invented for this
|
||||
// provider.
|
||||
type providerData struct {
|
||||
@@ -50,36 +50,36 @@ type providerData struct {
|
||||
alerting *client
|
||||
}
|
||||
|
||||
func (p *sentryProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
|
||||
resp.TypeName = "sentry"
|
||||
func (p *cairnobsProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
|
||||
resp.TypeName = "cairnobs"
|
||||
resp.Version = p.version
|
||||
}
|
||||
|
||||
func (p *sentryProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||
func (p *cairnobsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages Sentry log-aggregation-platform resources. Dashboards, alert rules, and notification targets for now -- tenant/RBAC resources are real, disclosed future work, not built in this pass; see the provider README.",
|
||||
Description: "Manages Cairn OBS log-aggregation-platform resources. Dashboards, alert rules, and notification targets for now -- 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,
|
||||
Description: "Base URL of the api service, e.g. \"http://localhost:8080\". Defaults to " +
|
||||
"$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).",
|
||||
"$CAIRNOBS_API_ENDPOINT, or \"http://localhost:8080\" if that's unset too -- same " +
|
||||
"default cairnobsctl's --api/$CAIRNOBSCTL_API_URL uses (cli/cmd/cairnobsctl/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.",
|
||||
"$CAIRNOBS_ALERTING_API_ENDPOINT, or \"http://localhost:8081\" if that's unset too -- " +
|
||||
"same default cairnobsctl's --alerting-api/$CAIRNOBSCTL_ALERTING_API_URL uses.",
|
||||
},
|
||||
"token": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Description: "Bearer credential sent as \"Authorization: Bearer <token>\" on every request " +
|
||||
"-- required once a deployment configures enterprise-auth (see " +
|
||||
"/docs/phase-4-rbac-design.md), same as sentryctl's $SENTRYCTL_TOKEN. Defaults to " +
|
||||
"$SENTRY_API_TOKEN if unset. Set via a variable or environment, never a literal in a " +
|
||||
"/docs/phase-4-rbac-design.md), same as cairnobsctl's $CAIRNOBSCTL_TOKEN. Defaults to " +
|
||||
"$CAIRNOBS_API_TOKEN if unset. Set via a variable or environment, never a literal in a " +
|
||||
".tf file committed to version control.",
|
||||
},
|
||||
},
|
||||
@@ -87,11 +87,11 @@ func (p *sentryProvider) Schema(_ context.Context, _ provider.SchemaRequest, res
|
||||
}
|
||||
|
||||
// Configure resolves endpoint/token the same precedence order
|
||||
// sentryctl's resolveAPIURL/resolveToken use (explicit config value,
|
||||
// cairnobsctl's resolveAPIURL/resolveToken use (explicit config value,
|
||||
// then an environment variable, then a hardcoded default) so behavior
|
||||
// stays predictable across both of this project's Sentry API clients.
|
||||
func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
|
||||
var config sentryProviderModel
|
||||
// stays predictable across both of this project's Cairn OBS API clients.
|
||||
func (p *cairnobsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
|
||||
var config cairnobsProviderModel
|
||||
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
@@ -99,7 +99,7 @@ func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRe
|
||||
|
||||
endpoint := config.Endpoint.ValueString()
|
||||
if endpoint == "" {
|
||||
endpoint = os.Getenv("SENTRY_API_ENDPOINT")
|
||||
endpoint = os.Getenv("CAIRNOBS_API_ENDPOINT")
|
||||
}
|
||||
if endpoint == "" {
|
||||
endpoint = "http://localhost:8080"
|
||||
@@ -107,7 +107,7 @@ func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRe
|
||||
|
||||
alertingEndpoint := config.AlertingEndpoint.ValueString()
|
||||
if alertingEndpoint == "" {
|
||||
alertingEndpoint = os.Getenv("SENTRY_ALERTING_API_ENDPOINT")
|
||||
alertingEndpoint = os.Getenv("CAIRNOBS_ALERTING_API_ENDPOINT")
|
||||
}
|
||||
if alertingEndpoint == "" {
|
||||
alertingEndpoint = "http://localhost:8081"
|
||||
@@ -115,7 +115,7 @@ func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRe
|
||||
|
||||
token := config.Token.ValueString()
|
||||
if token == "" {
|
||||
token = os.Getenv("SENTRY_API_TOKEN")
|
||||
token = os.Getenv("CAIRNOBS_API_TOKEN")
|
||||
}
|
||||
|
||||
data := &providerData{
|
||||
@@ -126,7 +126,7 @@ func (p *sentryProvider) Configure(ctx context.Context, req provider.ConfigureRe
|
||||
resp.ResourceData = data
|
||||
}
|
||||
|
||||
func (p *sentryProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||
func (p *cairnobsProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||
return []func() resource.Resource{
|
||||
newDashboardResource,
|
||||
newDashboardPanelResource,
|
||||
@@ -135,7 +135,7 @@ func (p *sentryProvider) Resources(_ context.Context) []func() resource.Resource
|
||||
}
|
||||
}
|
||||
|
||||
func (p *sentryProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
func (p *cairnobsProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
newDashboardDataSource,
|
||||
newDashboardPanelDataSource,
|
||||
|
||||
@@ -40,14 +40,14 @@ func TestDashboardResourceSchemaValid(t *testing.T) {
|
||||
newDashboardResource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_dashboard schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_dashboard schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{
|
||||
"id", "tenant_id", "name", "description",
|
||||
"default_earliest", "default_latest", "created_by", "created_at", "updated_at",
|
||||
} {
|
||||
if _, ok := resp.Schema.Attributes[attr]; !ok {
|
||||
t.Errorf("sentry_dashboard schema missing expected attribute %q", attr)
|
||||
t.Errorf("cairnobs_dashboard schema missing expected attribute %q", attr)
|
||||
}
|
||||
}
|
||||
if !resp.Schema.Attributes["name"].IsRequired() {
|
||||
@@ -60,9 +60,9 @@ func TestDashboardResourceSchemaValid(t *testing.T) {
|
||||
|
||||
func TestDashboardResourceMetadataSetsTypeName(t *testing.T) {
|
||||
resp := &resource.MetadataResponse{}
|
||||
newDashboardResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "sentry"}, resp)
|
||||
if resp.TypeName != "sentry_dashboard" {
|
||||
t.Fatalf("TypeName = %q, want sentry_dashboard", resp.TypeName)
|
||||
newDashboardResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "cairnobs"}, resp)
|
||||
if resp.TypeName != "cairnobs_dashboard" {
|
||||
t.Fatalf("TypeName = %q, want cairnobs_dashboard", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func TestDashboardPanelResourceSchemaValid(t *testing.T) {
|
||||
newDashboardPanelResource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_dashboard_panel schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_dashboard_panel schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{
|
||||
"id", "dashboard_id", "title", "query", "query_language", "viz_type", "viz_config",
|
||||
@@ -82,7 +82,7 @@ func TestDashboardPanelResourceSchemaValid(t *testing.T) {
|
||||
"earliest_override", "latest_override", "sort_order", "created_at", "updated_at",
|
||||
} {
|
||||
if _, ok := resp.Schema.Attributes[attr]; !ok {
|
||||
t.Errorf("sentry_dashboard_panel schema missing expected attribute %q", attr)
|
||||
t.Errorf("cairnobs_dashboard_panel schema missing expected attribute %q", attr)
|
||||
}
|
||||
}
|
||||
if !resp.Schema.Attributes["query"].IsRequired() {
|
||||
@@ -98,9 +98,9 @@ func TestDashboardPanelResourceSchemaValid(t *testing.T) {
|
||||
|
||||
func TestDashboardPanelResourceMetadataSetsTypeName(t *testing.T) {
|
||||
resp := &resource.MetadataResponse{}
|
||||
newDashboardPanelResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "sentry"}, resp)
|
||||
if resp.TypeName != "sentry_dashboard_panel" {
|
||||
t.Fatalf("TypeName = %q, want sentry_dashboard_panel", resp.TypeName)
|
||||
newDashboardPanelResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "cairnobs"}, resp)
|
||||
if resp.TypeName != "cairnobs_dashboard_panel" {
|
||||
t.Fatalf("TypeName = %q, want cairnobs_dashboard_panel", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestAlertRuleResourceSchemaValid(t *testing.T) {
|
||||
newAlertRuleResource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_alert_rule schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_alert_rule schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{
|
||||
"id", "tenant_id", "name", "description", "query", "query_language",
|
||||
@@ -120,7 +120,7 @@ func TestAlertRuleResourceSchemaValid(t *testing.T) {
|
||||
"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)
|
||||
t.Errorf("cairnobs_alert_rule schema missing expected attribute %q", attr)
|
||||
}
|
||||
}
|
||||
if !resp.Schema.Attributes["name"].IsRequired() {
|
||||
@@ -133,9 +133,9 @@ func TestAlertRuleResourceSchemaValid(t *testing.T) {
|
||||
|
||||
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)
|
||||
newAlertRuleResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "cairnobs"}, resp)
|
||||
if resp.TypeName != "cairnobs_alert_rule" {
|
||||
t.Fatalf("TypeName = %q, want cairnobs_alert_rule", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,14 +147,14 @@ func TestNotificationTargetResourceSchemaValid(t *testing.T) {
|
||||
newNotificationTargetResource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_notification_target schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_notification_target schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
for _, attr := range []string{
|
||||
"id", "tenant_id", "name", "kind", "webhook_url",
|
||||
"payload_template", "headers", "secret", "created_by",
|
||||
} {
|
||||
if _, ok := resp.Schema.Attributes[attr]; !ok {
|
||||
t.Errorf("sentry_notification_target schema missing expected attribute %q", attr)
|
||||
t.Errorf("cairnobs_notification_target schema missing expected attribute %q", attr)
|
||||
}
|
||||
}
|
||||
if !resp.Schema.Attributes["name"].IsRequired() {
|
||||
@@ -167,9 +167,9 @@ func TestNotificationTargetResourceSchemaValid(t *testing.T) {
|
||||
|
||||
func TestNotificationTargetResourceMetadataSetsTypeName(t *testing.T) {
|
||||
resp := &resource.MetadataResponse{}
|
||||
newNotificationTargetResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "sentry"}, resp)
|
||||
if resp.TypeName != "sentry_notification_target" {
|
||||
t.Fatalf("TypeName = %q, want sentry_notification_target", resp.TypeName)
|
||||
newNotificationTargetResource().Metadata(context.Background(), resource.MetadataRequest{ProviderTypeName: "cairnobs"}, resp)
|
||||
if resp.TypeName != "cairnobs_notification_target" {
|
||||
t.Fatalf("TypeName = %q, want cairnobs_notification_target", resp.TypeName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ func TestDashboardDataSourceSchemaValid(t *testing.T) {
|
||||
newDashboardDataSource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_dashboard data source schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_dashboard data source schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
if !resp.Schema.Attributes["id"].IsRequired() {
|
||||
t.Error(`"id" must be Required -- a data source needs it to know what to look up`)
|
||||
@@ -199,7 +199,7 @@ func TestDashboardPanelDataSourceSchemaValid(t *testing.T) {
|
||||
newDashboardPanelDataSource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_dashboard_panel data source schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_dashboard_panel data source schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
if !resp.Schema.Attributes["dashboard_id"].IsRequired() {
|
||||
t.Error(`"dashboard_id" must be Required`)
|
||||
@@ -220,7 +220,7 @@ func TestAlertRuleDataSourceSchemaValid(t *testing.T) {
|
||||
newAlertRuleDataSource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_alert_rule data source schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_alert_rule data source schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
if !resp.Schema.Attributes["id"].IsRequired() {
|
||||
t.Error(`"id" must be Required`)
|
||||
@@ -238,7 +238,7 @@ func TestNotificationTargetDataSourceSchemaValid(t *testing.T) {
|
||||
newNotificationTargetDataSource().Schema(ctx, req, resp)
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
t.Fatalf("sentry_notification_target data source schema has errors: %v", resp.Diagnostics)
|
||||
t.Fatalf("cairnobs_notification_target data source schema has errors: %v", resp.Diagnostics)
|
||||
}
|
||||
if !resp.Schema.Attributes["id"].IsRequired() {
|
||||
t.Error(`"id" must be Required`)
|
||||
@@ -253,14 +253,14 @@ func TestDataSourcesMetadataSetTypeNames(t *testing.T) {
|
||||
newDS func() datasource.DataSource
|
||||
wantType string
|
||||
}{
|
||||
{newDashboardDataSource, "sentry_dashboard"},
|
||||
{newDashboardPanelDataSource, "sentry_dashboard_panel"},
|
||||
{newAlertRuleDataSource, "sentry_alert_rule"},
|
||||
{newNotificationTargetDataSource, "sentry_notification_target"},
|
||||
{newDashboardDataSource, "cairnobs_dashboard"},
|
||||
{newDashboardPanelDataSource, "cairnobs_dashboard_panel"},
|
||||
{newAlertRuleDataSource, "cairnobs_alert_rule"},
|
||||
{newNotificationTargetDataSource, "cairnobs_notification_target"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
resp := &datasource.MetadataResponse{}
|
||||
c.newDS().Metadata(context.Background(), datasource.MetadataRequest{ProviderTypeName: "sentry"}, resp)
|
||||
c.newDS().Metadata(context.Background(), datasource.MetadataRequest{ProviderTypeName: "cairnobs"}, resp)
|
||||
if resp.TypeName != c.wantType {
|
||||
t.Errorf("TypeName = %q, want %q", resp.TypeName, c.wantType)
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,11 +1,11 @@
|
||||
// Command terraform-provider-sentry is Sentry's Terraform provider --
|
||||
// CLAUDE.md names it a first-class deliverable alongside sentryctl
|
||||
// Command terraform-provider-cairnobs is Cairn OBS's Terraform provider --
|
||||
// CLAUDE.md names it a first-class deliverable alongside cairnobsctl
|
||||
// ("CLI and Terraform provider are first-class, not afterthoughts"),
|
||||
// but this is the first phase to actually build any of it.
|
||||
//
|
||||
// Scoped deliberately narrow to start: one resource
|
||||
// (internal/provider.dashboardResource, sentry_dashboard), reusing the
|
||||
// exact same JSON contract cli/cmd/sentryctl's "apply" subcommand and
|
||||
// (internal/provider.dashboardResource, cairnobs_dashboard), reusing the
|
||||
// exact same JSON contract cli/cmd/cairnobsctl's "apply" subcommand and
|
||||
// web's dashboard export button already use against
|
||||
// api/dashboards.Handler -- "one JSON contract, multiple callers" is a
|
||||
// design decision made back in Phase 3 (see cli/README.md), this
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/providerserver"
|
||||
|
||||
"github.com/sentry/sentry/terraform/internal/provider"
|
||||
"github.com/cairnobs/cairnobs/terraform/internal/provider"
|
||||
)
|
||||
|
||||
// version is overridden at build time via -ldflags, same convention
|
||||
@@ -41,7 +41,7 @@ func main() {
|
||||
// required by the protocol even before actual registry
|
||||
// publication, since local dev overrides
|
||||
// (~/.terraformrc dev_overrides) key on this same address.
|
||||
Address: "registry.terraform.io/sentry/sentry",
|
||||
Address: "registry.terraform.io/cairnobs/cairnobs",
|
||||
Debug: debug,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user