Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ Built with Terraform Plugin Framework the provider uses GraphQL to communicate w

All commands use the `just` task runner:
- `just build` - Build the provider binary
- `just format` / `just lint` - Format and lint code
- `just lint` - Format and lint code
- `just test` - Run acceptance tests (replay mode, no API needed)
- `just test-unit` - Run unit tests only
- `just test-live` - Run tests against live API
- `just test-record <testname>` - Record new API responses
- `just docs` - Generate provider documentation
Expand All @@ -37,7 +38,7 @@ All commands use the `just` task runner:
### Adding Resources

1. Create model (`internal/models/`)
2. Create API methods (`internal/api/`)
2. Create API methods (`internal/api/platform/`)
3. Create resource (`internal/resources/`), register in `resources.go`
4. Add examples and tests
5. Run `just docs`
Expand Down
54 changes: 27 additions & 27 deletions dev-docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,35 @@
├── main.go # Provider entry point
├── internal/
│ ├── provider/ # Provider configuration and registration
│ ├── api/ # GraphQL API client and wrappers
│ ├── api/ # Shared HTTP transports and errors for API clients
│ │ └── platform/ # Platform GraphQL API client and wrappers
│ ├── resources/ # Terraform resource implementations
│ ├── datasources/ # Terraform data source implementations
│ ├── models/ # Data model definitions
│ ├── acceptance_tests/ # Acceptance tests with HTTP recording
│ ├── errors/ # Error handling utilities (legacy)
│ ├── modelupdate/ # Helper functions for model updates
│ ├── planmodifiers/ # Custom Terraform plan modifiers
│ ├── providerdata/ # Provider data management
│ ├── schemavalidate/ # Custom schema validators
│ ├── schemadefault/ # Custom schema default value handlers
│ └── typehelpers/ # Type conversion and manipulation helpers
├── examples/ # Example Terraform configurations
├── docs/ # Auto-generated documentation
├── templates/ # Documentation templates
└── tools/ # Build and documentation tools
```

## API Integration

- **GraphQL Client**: Uses `github.com/hasura/go-graphql-client` for GraphQL communication
- **API Wrapper**: `internal/api/api.go` provides a typed `API` struct with methods for each resource type
- **Shared API code** (`internal/api/`): Functionality common to all API clients
- HTTP transport layers for authentication (`AuthTransport`) and logging (`LogTransport`), combined by `NewTransport()`
- Error types: `APIError`, `NotFound` and `Conflict` (resources remove objects from state on `api.NotFound`)
- `MaskSecrets()` returns a context which masks the given secrets in request/response logs. Call it before API requests that send secrets
- **GraphQL Client**: The platform client (`internal/api/platform/`) uses `github.com/hasura/go-graphql-client` for GraphQL communication
- **API Wrapper**: `internal/api/platform/api.go` provides a typed `API` struct with methods for each resource type
- **Modular API Files**: Each resource type has its own API file
- **HTTP Transport**: Custom transport layers for authentication (`authTransport`) and logging (`logTransport`)
- **Enums**: Strongly typed enums in `internal/api/enums.go`.
- **Pagination**: Generic helpers in `internal/api/pagination.go` for GraphQL connection pattern pagination
- `FindInPaginatedQuery[T, R]()` - Searches through paginated results for a specific item
- `CollectAllPages[T]()` - Collects all items from all pages
- **Filtering**: Filter API in `internal/api/filter.go` for constructing GraphQL filter queries
- `FilterElementInput` and `FilterValueInput` types for building filter expressions
- **Enums**: Strongly typed enums in `internal/api/platform/enums.go`.
- **Filtering**: Filter API in `internal/api/platform/filter.go` for constructing GraphQL filter queries
- `filterElementInput` and `filterValueInput` types for building filter expressions
- `newExactMatchFilter()` helper for creating exact-match filters with "equals" operator

## Resource Architecture
Expand All @@ -55,7 +54,7 @@ Resources follow the Terraform Plugin Framework patterns:
- `Delete()` - Delete resource
- `ImportState()` - Import existing resource

3. **Resource Registration**: All resources are registered in `internal/resources/resources.go` in the `RESOURCES` slice
3. **Resource Registration**: All resources are registered in the `Resources` variable in `internal/resources/resources.go`, via `newFactory()`. Resources in the `Unreleased` list are only enabled when `STACKLET_UNRELEASED_FEATURES` is set

4. **Model Separation**: Models are defined in `internal/models/` separate from resource logic

Expand All @@ -67,6 +66,7 @@ Resources follow the Terraform Plugin Framework patterns:
- `RequiresReplaceIfFieldsChanged()` - Forces recreation when specific nested object fields change
- `RequiresReplaceIfUnset()` - Forces recreation when object transitions from non-null to null
- `RequiresReplaceIfNullStringChange()` - Forces recreation when string toggles between null and non-null
- `TrimWhiteSpace()` - Strips leading/trailing whitespace from planned strings, for APIs that normalize it
- `DefaultObject()` - Sets default object values to match API defaults

## Data Source Architecture
Expand All @@ -77,7 +77,7 @@ Data sources follow similar patterns to resources:
- `datasource.DataSource` - Basic data source interface
- `datasource.DataSourceWithConfigure` - For provider configuration

2. **Data Source Registration**: All registered in `internal/datasources/datasources.go` in the `DATASOURCES` slice
2. **Data Source Registration**: All registered in the `DataSources` variable in `internal/datasources/datasources.go`, with the same `Released`/`Unreleased` split as resources

3. **Shared Models**: Often share model definitions with their corresponding resources

Expand All @@ -91,18 +91,21 @@ Models in `internal/models/` define Terraform schema structure with `tfsdk` stru

## Type Helpers and Validators

**Type Helpers** (`internal/typehelpers/`): All return `(result, diag.Diagnostics)` for proper error propagation:
**Type Helpers** (`internal/typehelpers/`): Helpers that can fail return `(result, diag.Diagnostics)` for proper error propagation:
- `JSONString()` - Normalizes JSON strings for consistent comparison
- `ObjectValue()` / `ObjectList()` - Converts values/slices to Terraform objects/lists
- `FilteredObject()` / `UpdatedObject()` - Extracts/updates object attributes
- `ObjectStringIdentifier()` / `ListItemsIdentifiers()` - Extracts identifiers
- `ListSortedEntries()` - Sorts list entries by identifier order
- `StringsList()` / `GraphQLIDValue()` - Simple conversions (no diagnostics)
- `PreserveIfNull()` / `PreserveIfEmptyJSON()` - Keep the original value when the API returns null or `{}` for it, avoiding perpetual diffs

**Usage Pattern**: Call helper, append diagnostics with `diags.Append(d...)`, check `diags.HasError()` for early returns when needed.

**Schema Validators** (`internal/schemavalidate/`):
- `OneOfCloudProviders()` - Validates cloud provider values
- `unique_string_attribute.go` - Ensures string uniqueness in lists
- `UniqueStringAttribute()` - Ensures string uniqueness in lists
- `UUID()` - Validates UUID strings

**Schema Defaults** (`internal/schemadefault/`):
- `EmptyListDefault()` / `EmptyMapDefault()` - Provide empty defaults for optional attributes
Expand All @@ -113,14 +116,10 @@ Models in `internal/models/` define Terraform schema structure with `tfsdk` stru
- **Legacy package** - Being phased out in favor of direct `diag.Diagnostics` usage
- New code should use `diag.Diagnostics` directly and the `typehelpers` package functions which all return diagnostics

**Model Update** (`internal/modelupdate/`):
- Helper functions for updating Terraform models from API results
- Centralizes common update patterns

**Provider Data** (`internal/providerdata/`):
- `ProviderData` struct holds API client for resources/data sources
- `GetResourceProviderData()` - Retrieves provider data in resources
- `GetDataSourceProviderData()` - Retrieves provider data in data sources
- `providerData` struct holds API clients for resources/data sources, built by `New()`
- `GetForResource()` - Retrieves provider data in resources
- `GetForDataSource()` - Retrieves provider data in data sources

## Architectural Patterns

Expand All @@ -130,7 +129,7 @@ All functions that can fail return `diag.Diagnostics` (plural). Pattern: initial

### Type Conversion Pattern

Use `internal/typehelpers` for all type conversions: `JSONString()`, `ObjectValue()`, `ObjectList()`, `ObjectStringIdentifier()`, `ListItemsIdentifiers()`, `ListSortedEntries()`. All return `(result, diag.Diagnostics)`.
Use `internal/typehelpers` for type conversions: `JSONString()`, `ObjectValue()`, `ObjectList()`, `ObjectStringIdentifier()`, `ListItemsIdentifiers()`, `ListSortedEntries()`. These return `(result, diag.Diagnostics)`.

### Model Update Pattern

Expand All @@ -142,11 +141,11 @@ Models implement `Update(ctx, obj) diag.Diagnostics`. Resource models extend dat
- One resource/data source per file
- File names match resource type (e.g., `account.go` for `stacklet_account`)
- Models in separate `models/` package
- API methods in separate `api/` package
- API methods in the `api/platform/` package

### Naming Conventions
- Resource constructors: `NewXxxResource()`
- Data source constructors: `NewXxxDataSource()`
- Resource types: `xxxResource`, registered with `newFactory(&xxxResource{})`
- Data source types: `xxxDataSource`, registered with `newFactory(&xxxDataSource{})`
- API types: Input types use `XxxInput` suffix
- Models: Separate types for resources vs data sources when needed

Expand All @@ -163,6 +162,7 @@ Models implement `Update(ctx, obj) diag.Diagnostics`. Resource models extend dat
- `RequiresReplaceIfNullStringChange()` - For string fields that toggle between null and non-null
- `RequiresReplaceIfFieldsChanged()` - For objects where specific nested fields require replacement
- `RequiresReplaceIfUnset()` - For objects that cannot transition from configured to null
- `TrimWhiteSpace()` - For strings where surrounding whitespace is not significant
- `DefaultObject()` - Sets default objects matching API defaults

### Write-Only Fields
Expand Down
12 changes: 6 additions & 6 deletions dev-docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
## Adding a New Resource

1. Create model in `internal/models/`
2. Create API methods in `internal/api/`
2. Create API methods in `internal/api/platform/`
3. Create resource in `internal/resources/`
4. Register in `internal/resources/resources.go` in the `RESOURCES` slice
4. Register in the `Resources` variable in `internal/resources/resources.go`
5. Add examples in `examples/resources/`
6. Add acceptance test in `acceptance_tests/`
6. Add acceptance test in `internal/acceptance_tests/`
7. Generate docs: `just docs`

## Adding a New Data Source

1. Create or reuse model in `internal/models/`
2. Create or reuse API methods in `internal/api/`
2. Create or reuse API methods in `internal/api/platform/`
3. Create data source in `internal/datasources/`
4. Register in `internal/datasources/datasources.go` in the `DATASOURCES` slice
4. Register in the `DataSources` variable in `internal/datasources/datasources.go`
5. Add examples in `examples/data-sources/`
6. Add acceptance test in `acceptance_tests/`
6. Add acceptance test in `internal/acceptance_tests/`
7. Generate docs: `just docs`

**Note**: Data sources often reuse models and API methods from corresponding resources.
Expand Down
15 changes: 8 additions & 7 deletions dev-docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ Run `./terraform-provider-stacklet -debug` and use `TF_REATTACH_PROVIDERS` envir
All development commands use the `just` task runner:

- `just build` - Build the provider binary
- `just format` / `just lint` - Format and lint code (Go, Terraform, docs, copyright)
- `just lint` - Format and lint code (Go, Terraform, docs, copyright)
- `just test` - Run acceptance tests (replay mode, no API needed)
- `just test-unit` - Run unit tests only
- `just test-live` - Run tests against live API
- `just test-record <testname>` - Record new API responses for a test
- `just docs` - Generate provider documentation
Expand All @@ -35,15 +36,15 @@ Testing individual tests: `just test -run TestAccAccountResource`

## Testing Strategy

**HTTP Recording**: Tests use recorded HTTP interactions (`acceptance_tests/recordings/`) for fast, deterministic runs without live API. Three modes via `TF_ACC_MODE`: `replay` (default), `record`, `live`. See `acceptance_tests/recorded_transport.go`.
**HTTP Recording**: Tests use recorded HTTP interactions (`internal/acceptance_tests/recordings/`) for fast, deterministic runs without live API. Three modes via `TF_ACC_MODE`: `replay` (default), `record`, `live`. See `internal/acceptance_tests/recorded_transport.go`.

**Test Helpers**:
- `importStateIDFuncFromAttrs()` - Creates import IDs from resource attributes
- `prefixName()` - Adds test prefixes via `TF_ACC_PREFIX`
- `ConfigPlanChecks` with `plancheck.ExpectResourceAction()` - Validates plan actions
- Template rendering for test configurations

**Unit Tests**: Use testify test suites for shared setup (e.g., `internal/provider/provider_test.go`). Run with: `go test ./internal/provider/`
**Unit Tests**: Table-driven tests with testify assertions (e.g., `internal/provider/provider_test.go`). Run with: `just test-unit`

## Authentication

Expand All @@ -62,16 +63,16 @@ Additional tools are defined in `tools/go.mod`.
## Documentation

- **Auto-generated**: Documentation in `docs/` is generated from code
- **Templates**: Use `templates/` for customizing documentation
- **Examples**: All examples in `examples/` must be valid Terraform
- **Import examples**: Each resource has `import.sh` example

## CI/CD

### Continuous Integration (`.github/workflows/ci.yaml`)
- Runs on push to `main` and all PRs
- All GitHub Actions use SHA pinning for security
- Separate jobs for: Go linting, Terraform linting, documentation validation, copyright headers, and acceptance tests (replay mode)
- Runs on push to `main` and PRs targeting `main`
- All GitHub Actions and reusable workflows use SHA pinning for security
- `Lint` job runs `just lint` (all pre-commit hooks: Go linting, Terraform formatting and examples validation, documentation validation, copyright headers, spelling)
- `Tests` job runs `just test` (acceptance tests in replay mode)

### Release Workflow (`.github/workflows/release.yaml`)
- **Public Registry**: Triggered by version tags (`v*.*.*`), builds with GoReleaser, signs with GPG, publishes to Terraform Registry
Expand Down
Loading
Loading