Skip to main content

materialize-terraform-provider-dev

Contributing to the Materialize Terraform provider codebase. Covers the project layout (pkg/materialize, pkg/resources, pkg/datasources, pkg/provider), the builder pattern for SQL generation, adding new resources and data sources end to end, the acceptance test framework, unit testing with sqlmock, CI pipelines, release flow via goreleaser, and codebase conventions. Use this skill whenever the user wants to add a new Terraform resource or data source, modify an existing resource, write or run provider tests, understand the provider architecture, debug test failures, cut a release, or contribute a fix to the terraform-provider-materialize codebase. Also trigger when the user mentions pkg/materialize, pkg/resources, acceptance tests, mock scans, the builder pattern in this provider, or GNUmakefile targets.

Quellinformationen

Repository
MaterializeInc/terraform-provider-materialize
Letzte Quellaktivität
21. Juli 2026 um 19:13
Erkannte Sprache von SKILL.md
Englisch
Sterne
15
Forks
13

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
materialize-terraform-provider-dev
description
Contributing to the Materialize Terraform provider codebase. Covers the project layout (pkg/materialize, pkg/resources, pkg/datasources, pkg/provider), the builder pattern for SQL generation, adding new resources and data sources end to end, the acceptance test framework, unit testing with sqlmock, CI pipelines, release flow via goreleaser, and codebase conventions. Use this skill whenever the user wants to add a new Terraform resource or data source, modify an existing resource, write or run provider tests, understand the provider architecture, debug test failures, cut a release, or contribute a fix to the terraform-provider-materialize codebase. Also trigger when the user mentions pkg/materialize, pkg/resources, acceptance tests, mock scans, the builder pattern in this provider, or GNUmakefile targets.
# Materialize Terraform Provider: Development Guide This skill covers contributing to the `terraform-provider-materialize` codebase. For using the provider as an end user, see the `materialize-terraform-provider` skill instead. ## How to Use This Skill 1. **Adding a new resource**: Follow the end-to-end walkthrough in the "Adding a Resource" section. 2. **Understanding the architecture**: Read the "Architecture" section for the three-tier layout. 3. **Running tests**: See "Testing" for unit, acceptance, and integration test workflows. 4. **Debugging a test failure**: Check "Debugging" and the common gotchas. 5. **Cutting a release**: See "Release Flow". Key reference files in the repo: `DESIGN.md` (architecture patterns), `CONTRIBUTING.md` (build/test instructions), `GNUmakefile` (build targets). ## Build Commands | Command | Purpose | |---------|---------| | `make install` | Build and install provider to `~/.terraform.d/plugins/` | | `make test` | Run unit tests | | `make testacc` | Run acceptance tests (requires `docker compose up -d`) | | `make docs` | Generate docs from schema (`go generate ./...`) | | `make fmt` | Format Go and Terraform code | ## Architecture Three-tier design. Each tier has a clear responsibility. ### Tier 1: Materialize layer (`pkg/materialize/`) SQL builders and query definitions. About 107 files, one per object type. Every object follows the builder pattern (see `DESIGN.md`): ```go // Builder: constructs SQL statements type DatabaseBuilder struct { ddl Builder databaseName string } func (b *DatabaseBuilder) Create() error { q := fmt.Sprintf(`CREATE DATABASE %s;`, QuoteIdentifier(b.databaseName)) return b.ddl.exec(q) } func (b *DatabaseBuilder) Drop() error { return b.ddl.drop(QuoteIdentifier(b.databaseName)) } // Query: reads from mz_catalog type DatabaseParams struct { DatabaseId sql.NullString `db:"id"` DatabaseName sql.NullString `db:"database_name"` OwnerName sql.NullString `db:"owner_name"` } var databaseQuery = NewBaseQuery(` SELECT mz_databases.id, mz_databases.name AS database_name, ... FROM mz_databases JOIN mz_roles ON mz_databases.owner_id = mz_roles.id `) ``` Key utilities: - `QuoteIdentifier(s)`: double-quote SQL identifiers - `QuoteString(s)`: single-quote SQL string literals - `QualifiedName(parts...)`: join as `"db"."schema"."object"` - `NewBaseQuery(sql)` + `.QueryPredicate(map)`: build filtered queries - `MaterializeObject`: struct with `Name`, `SchemaName`, `DatabaseName`, `ObjectType` ### Tier 2: Resources and data sources (`pkg/resources/`, `pkg/datasources/`) Terraform resource definitions. About 151 resource files and 55 data source files. Each resource has: - Schema definition (map of `*schema.Schema`) - Constructor returning `*schema.Resource` with CRUD functions - CRUD functions: `<type>Create`, `<type>Read`, `<type>Update`, `<type>Delete` - Optional import function Schema helpers in `pkg/resources/schema.go`: - `ObjectNameSchema(resource, required, forceNew)` for the `name` field - `CommentSchema(required)`, `OwnershipRoleSchema()`, `RegionSchema()` - `DatabaseNameSchema()`, `SchemaNameSchema()` for namespace fields - `IdentifierSchema()` for object references - `ValueSecretSchema()` for text-or-secret fields - `FormatSpecSchema()` for complex format blocks (Avro, Protobuf, CSV) ### Tier 3: Provider registration (`pkg/provider/`) `provider.go` registers all resources and data sources in `ResourcesMap` and `DataSourcesMap`. Also contains acceptance test infrastructure (`provider_test.go`). ### Supporting packages - `pkg/clients/`: Database connection management (pgx/sqlx) - `pkg/frontegg/`: Cloud/SaaS API client (Frontegg auth) - `pkg/testhelpers/`: Mock database setup, shared mock scan functions - `pkg/utils/`: ID extraction, region transforms, DB client helpers ## Adding a Resource End to End Follow these steps when adding a new resource. The same pattern applies to adding fields to existing resources (just skip the registration step). See `CONTRIBUTING.md` for a concrete example using cluster `size`. ### Step 1: Builder in `pkg/materialize/` Create `pkg/materialize/<type>.go`: ```go type MyResourceBuilder struct { ddl Builder name string // fields matching SQL parameters } func NewMyResourceBuilder(conn *sqlx.DB, obj MaterializeObject) *MyResourceBuilder { return &MyResourceBuilder{ddl: Builder{conn, MyResourceType}, name: obj.Name} } // Fluent setters func (b *MyResourceBuilder) Size(s string) *MyResourceBuilder { b.size = s; return b } // SQL generation func (b *MyResourceBuilder) Create() error { ... } func (b *MyResourceBuilder) Drop() error { ... } // Query layer type MyResourceParams struct { ... } var myResourceQuery = NewBaseQuery(`SELECT ... FROM ...`) func ScanMyResource(conn *sqlx.DB, id string) (MyResourceParams, error) { ... } func MyResourceId(conn *sqlx.DB, obj MaterializeObject) (string, error) { ... } ``` Convention: parameter names should match Materialize SQL syntax as closely as possible (see `DESIGN.md`). ### Step 2: Resource in `pkg/resources/` Create `pkg/resources/resource_<type>.go`: ```go func MyResource() *schema.Resource { return &schema.Resource{ CreateContext: myResourceCreate, ReadContext: myResourceRead, UpdateContext: myResourceUpdate, DeleteContext: myResourceDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: myResourceSchema, } } ``` CRUD pattern: - **Create**: Build object, execute SQL, query `mz_catalog` by name to get ID, call `d.SetId()` with region-prefixed ID, then call Read. - **Read**: Scan by ID from `mz_catalog`. If `sql.ErrNoRows`, call `d.SetId("")` and return nil (resource was deleted externally). Otherwise populate all schema fields with `d.Set()`. - **Update**: Check `d.HasChange("field")` for each mutable field. Apply changes via builder. - **Delete**: Build and execute DROP. ID management: - After CREATE, query by name to get the `mz_catalog` ID - Store as: `utils.TransformIdWithRegion(string(region), id)` - Extract with: `utils.ExtractId(d.Id())` ### Step 3: Register in `pkg/provider/provider.go` Add to `ResourcesMap`: ```go "materialize_my_resource": resources.MyResource(), ``` ### Step 4: Data source in `pkg/datasources/` (optional) Create `pkg/datasources/datasource_<type>.go` with a Read function that lists objects and populates a TypeList schema. Register in `DataSourcesMap`: ```go "materialize_my_resource": datasources.MyResource(), ``` ### Step 5: Mock scans in `pkg/testhelpers/mock_scans.go` Add a `MockMyResourceScan` function that sets up `sqlmock` expectations matching your query. ### Step 6: Unit tests Create `pkg/resources/resource_<type>_test.go` and optionally `pkg/materialize/<type>_test.go`. Unit tests use `testhelpers.WithMockProviderMeta()` to set up a mock database: ```go func TestResourceMyResourceCreate(t *testing.T) { in := map[string]interface{}{"name": "test_resource"} d := schema.TestResourceDataRaw(t, MyResource().Schema, in) testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`CREATE ...`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectQuery(`SELECT ...`).WillReturnRows(...) if err := myResourceCreate(context.TODO(), d, db); err != nil { t.Fatal(err) } }) } ``` Every `conn.Get()` or `conn.Exec()` in the code path needs a corresponding mock expectation. Missing mocks cause "unexpected query" test failures. ### Step 7: Acceptance tests Create `pkg/provider/acceptance_<type>_test.go`: ```go func TestAccMyResource_basic(t *testing.T) { name := acctest.RandStringFromCharSet(10, acctest.CharSetAlpha) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProviderFactories: testAccProviderFactories, CheckDestroy: nil, Steps: []resource.TestStep{ { Config: fmt.Sprintf(`resource "materialize_my_resource" "test" { name = "%s" }`, name), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("materialize_my_resource.test", "name", name), ), }, { ResourceName: "materialize_my_resource.test", ImportState: true, ImportStateVerify: true, }, }, }) } ``` Acceptance tests require `docker compose up -d` and `/etc/hosts` entries: ``` 127.0.0.1 materialized frontegg cloud ``` ### Step 8: Generate docs ```bash make docs ``` This generates `docs/resources/<type>.md` and `docs/data-sources/<type>.md` from the schema. ## Testing ### Unit tests ```bash make test ``` Run a specific test: ```bash go test -v ./pkg/resources/ -run TestResourceMyResourceCreate ``` Coverage target: 50% minimum (enforced in CI). Unit tests should cover the breadth of SQL variations for each resource. ### Acceptance tests ```bash docker compose up -d --build make testacc ``` Run a specific acceptance test: ```bash TF_ACC=1 go test -v ./pkg/provider/ -run TestAccMyResource_basic -timeout 1h ``` Acceptance tests run real Terraform commands against a Materialize instance in Docker. They should cover create, update, destroy, and import but do not need to exercise every SQL permutation. ### Integration tests Full Terraform projects in `integration/` that apply and destroy all resources: ```bash docker compose up -d docker exec provider terraform init docker exec provider terraform apply -auto-approve docker exec provider terraform plan -detailed-exitcode docker exec provider terraform destroy -auto-approve ``` For self-managed tests, use `--workdir /usr/src/app/integration/self_hosted`. Clean up stale state before re-running: delete `.terraform/`, `.terraform.lock.hcl`, and `terraform.tfstate*` in the integration directory. ## Debugging Enable Terraform debug logging: ```bash TF_LOG=DEBUG terraform apply ``` Check database state during acceptance tests: ```bash docker compose exec materialized psql -U materialize -d materialize -c "SELECT * FROM mz_tables LIMIT 5;" ``` ## CI Pipelines | Workflow | Trigger | What it does | |----------|---------|-------------| | `test.yml` | PR touching pkg/, main.go, go.mod | Unit tests with 50% coverage gate | | `acceptance.yml` | PR | Acceptance tests against Docker Compose | | `integration.yml` | PR | Full integration test (apply/plan/destroy) | | `gofmt.yml` | PR | Go formatting check | | `terraform.yml` | PR | Terraform HCL syntax check | | `documentation.yml` | PR | Docs generation check | | `release.yml` | Tag push (v*) | Goreleaser build, GPG sign, publish to GitHub Releases | ## Release Flow Tag and push: ```bash git tag -a vX.Y.Z -m vX.Y.Z git push origin vX.Y.Z ``` This triggers `.goreleaser.yml` via GitHub Actions. Builds binaries for all platforms, signs with GPG, and publishes to GitHub Releases. The Terraform Registry picks up the release automatically. ## Codebase Conventions **File naming:** - Resources: `resource_<type>.go` + `resource_<type>_test.go` in `pkg/resources/` - Data sources: `datasource_<type>.go` + `datasource_<type>_test.go` in `pkg/datasources/` - Builders: `<type>.go` in `pkg/materialize/` - Acceptance tests: `acceptance_<type>_test.go` in `pkg/provider/` - Mock scans: `pkg/testhelpers/mock_scans.go` **Resource naming:** Names must match Materialize SQL exactly. A load generator source is `materialize_source_load_generator` (matching `CREATE SOURCE ... FROM LOAD GENERATOR`). See `DESIGN.md`. **Dividing resources:** Complex objects with many contradictory parameters get separate resources (e.g., `materialize_source_kafka` vs `materialize_source_postgres`), not one monolithic resource. **SQL safety:** Always use `QuoteIdentifier()` for identifiers and `QuoteString()` for string literals. Never interpolate user input directly. **Nested types:** Prefer typed structs over `map[string]interface{}` for builder parameters (see `DESIGN.md`). **IDs from mz_catalog:** Resource IDs always come from `mz_catalog` system tables, never from SQL return values. After CREATE, query by name to get the ID. **Ownership and comments:** Use the shared helpers `applyOwnership()` and `applyComment()` after resource creation.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen