Manages Cosmoner resources from Terraform, through the same public API and API keys as the SDKs and the CLI.
terraform {
required_providers {
cosmoner = {
source = "datablock-dev/cosmoner"
}
}
}
provider "cosmoner" {}
resource "cosmoner_variable" "log_level" {
name = "LOG_LEVEL"
environment = "production"
value = "info"
}
resource "cosmoner_secret" "database_password" {
name = "DB_PASSWORD"
environment = "production"
value_wo = var.database_password
value_wo_version = 1
}The provider reads the CLI's environment variables, so a shell or CI job already
set up for cosmoner works unchanged:
| Variable | Provider argument | Notes |
|---|---|---|
COSMONER_API_KEY |
api_key |
Required. Keep it in the environment, not HCL. |
COSMONER_PROJECT_ID |
project_id |
Default project for every resource. |
COSMONER_API_URL |
api_url |
Defaults to https://api.cosmoner.com. |
A value set in the provider block wins over the environment.
| Type | Kind | Scopes the key needs |
|---|---|---|
cosmoner_secret |
resource | secrets:read, secrets:write, owner or admin |
cosmoner_variable |
resource | variables:read, variables:write, owner or admin |
cosmoner_webhook_endpoint |
resource | webhooks:read, webhooks:write |
cosmoner_ssh_key |
resource | servers:read, servers:write |
cosmoner_resource_group |
resource | projects:read, projects:write |
cosmoner_app |
resource | apps:read, apps:write |
cosmoner_postgres_database |
resource | databases:read, databases:write |
cosmoner_redis_database |
resource | redis:read, redis:write |
cosmoner_bucket |
resource | object-storage:read, object-storage:write |
cosmoner_project |
data source | projects:read |
Every resource takes an optional project_id that overrides the provider's,
and imports as <project_id>/<id>, or as a bare <id> when the provider has a
default project. docs/ has the full reference for each one.
Apps, PostgreSQL databases, Redis databases and buckets are charged to the
project's saved card from the moment they exist. apply waits until each one
is ready, up to the create time in its timeouts block. One that fails to
become ready, or is still starting at the deadline, stays in state as
tainted: the next apply replaces it, and terraform destroy removes it.
The provider never deletes it on its own, so a failed deploy's logs are still
there to read. A create the API refuses leaves nothing behind.
Databases and Redis have no resize route, so changing their size or plan
replaces them, data included. Use lifecycle { prevent_destroy = true } on
anything holding data you care about.
The API never returns a secret's value after setting it, so the provider cannot
compare the stored value with the configured one. It watches the secret's
version instead, which every write bumps. When the version moves without
Terraform, someone set the value elsewhere, and the next plan sets the
configured value again.
value_wo (Terraform 1.11+) keeps the value out of state and plan entirely.
Terraform cannot see a change to a write-only value, so bump value_wo_version
to send a new one. value works on any version, but is stored in state as a
sensitive value.
go test ./... # unit tests, plus Terraform runs against a fake API
golangci-lint run ./...
go generate ./... # regenerates docs/ — commit the resultThe provider tests run a real terraform binary through plan, apply, refresh
and import, against an in-process fake of the API in
internal/provider/fake_api_test.go. They need no credentials. The harness uses
the terraform on PATH, or TF_ACC_TERRAFORM_PATH, and downloads one if it
finds neither. Set CHECKPOINT_DISABLE=1 to skip HashiCorp's version check.
The fake mirrors the platform's routes, status codes and quirks: secrets that never return their value, descriptions kept unless one is sent, keys whitespace-normalised. When the platform changes one of those, change the fake with it, or the tests prove behaviour the real API no longer has.
docs/ is generated by tfplugindocs
from the schema descriptions and examples/, and CI fails if it is stale.
Write the documentation in the schema's MarkdownDescription, never in
docs/ itself.
To try a local build in a real configuration, build it and point Terraform at it with a development override:
go build -o "$HOME/.terraform.d/dev/terraform-provider-cosmoner" .# ~/.terraformrc
provider_installation {
dev_overrides {
"datablock-dev/cosmoner" = "/home/you/.terraform.d/dev"
}
direct {}
}Push a version tag:
git tag v0.1.0
git push origin v0.1.0.github/workflows/release.yml runs GoReleaser, which builds every platform,
writes SHA256SUMS and the Registry manifest, signs the checksums with GPG and
attaches everything to a GitHub release. The Terraform Registry picks the
release up from there.
A tag whose release job failed does not need a new tag: once the cause is fixed, re-run the job from the Actions tab and it builds the tagged commit again.
Tags follow semantic versioning, and the Registry treats them as the provider's version: a breaking schema change (a removed attribute, a renamed resource, a new required argument) is a major version.
Nothing is published until all of this is done. The v0.1.0 tag was pushed
before it was, and its release job stopped at the GPG import, so no GitHub
release and nothing on the Registry came of it.
-
Generate an RSA signing key (the Registry does not accept ECC keys) and export both halves:
gpg --full-generate-key # RSA and RSA, 4096 bits gpg --armor --export-secret-keys <fingerprint> > private.asc gpg --armor --export <fingerprint> > public.asc
-
Add the private half and its passphrase as Actions secrets on this repository:
GPG_PRIVATE_KEY(the whole ofprivate.asc) andPASSPHRASE. The release job checks for the key first and says so when it is missing. -
Sign in to registry.terraform.io with GitHub and allow it access to the
datablock-devorganization. If the organization restricts third-party OAuth apps, an owner has to approve the Terraform Registry under the organization's OAuth app policy first, or the namespace never appears. -
Under User Settings → Signing Keys, add
public.ascto thedatablock-devnamespace. The Registry verifies every release'sSHA256SUMS.sigagainst it. -
Cut a release (above) and check that the GitHub release carries the zips,
…_SHA256SUMS,…_SHA256SUMS.sigand…_manifest.json. -
Publish → Provider, choose
datablock-dev/terraform-provider-cosmoner. The Registry ingests the existing releases and installs a webhook, so every later tag is picked up without visiting it again.
source = "datablock-dev/cosmoner" and registryAddress in main.go both
follow from the namespace. Publishing from another one changes both.