implement-datasource
Provides standard procedures for building and modifying Terraform provider data sources.
Install
mkdir -p .claude/skills/implement-datasource && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/19244" && unzip -o skill.zip -d .claude/skills/implement-datasource && rm skill.zipInstalls to .claude/skills/implement-datasource
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
How to add or modify a Terraform data source in this provider. Covers scaffolding, unknown input handling, list data sources with pagination, and API response validation.Key capabilities
- →Add data source definition to `datasources.yml`
- →Generate schema and model types
- →Scaffold the data source using a command
- →Implement the data source with mapping conventions
- →Handle unknown input during `terraform plan`
- →Validate API responses and register the data source
How it works
This skill guides the implementation of a Terraform data source by defining it in `datasources.yml`, generating code, scaffolding, and then implementing the data source with specific mapping conventions. It includes critical steps for handling unknown inputs and validating API responses.
Inputs & outputs
When to use implement-datasource
- →Adding new Terraform data sources
- →Modifying existing providers
- →Implementing API response validation
About this skill
Implement Data Source
Step-by-step guide for adding a new data source or modifying an existing one. Before starting, also read the implementation-conventions skill for shared patterns.
Step 1: Add to datasources.yml
Add the data source definition to OpenAPI/1_tfplugingen-openapi/datasources.yml:
data_sources:
- name: my_resource
schema:
attributes:
overrides:
- name: id
data_source:
attribute_type:
name: string
optional: false # Make ID required input
Step 2: Generate Code
make generate
This generates the schema and model types in internal/provider/datasource_<name>/.
Step 3: Scaffold the Data Source
Always use the scaffold command — do NOT write from scratch:
go run github.com/doitintl/terraform-plugin-codegen-framework/cmd/tfplugingen-framework scaffold data-source \
--name <name> \
--output-file <name>_data_source.go \
--package provider \
--output-dir internal/provider
NOTE:
--output-filemust be the basename only (e.g.,my_data_source.go), not a full path.
Required interface declarations (verify in scaffolded file):
var _ datasource.DataSource = (*myDataSource)(nil)
var _ datasource.DataSourceWithConfigure = (*myDataSource)(nil)
Step 4: Implement the Data Source
Mapping Convention
Data sources that share an entity with a resource (e.g. alert, report) should reuse the
mapXxxToModel helper from the companion <name>.go file. Data sources with their own unique
mapping (e.g. list data sources) should place helpers in the _data_source.go file or in a
companion file when the mapping is non-trivial.
Simple data sources that only do a few scalar assignments may keep mapping logic inline in Read.
Implementation
- Use
*models.ClientWithResponsesfor client type - Use generated constructors (
NewXxxValue()) for nested objects - Add timeout support (see implementation-conventions)
Unknown Input Handling (Critical)
Data sources are read during terraform plan. If an input depends on an unresolved resource, its value will be Unknown. Check for unknown inputs before making API calls:
Scalar inputs — check with IsUnknown():
if data.Id.IsUnknown() {
data.Result = types.StringUnknown()
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
return
}
Composite inputs (lists, maps, objects) — use IsFullyKnown():
if !req.Config.Raw.IsFullyKnown() {
data.Id = types.StringUnknown()
data.Results = types.ListUnknown(elemType)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
return
}
A list can be known while containing unknown elements (e.g., resources = [some_resource.id]), which IsUnknown() on the list itself would miss.
IMPORTANT: Return
Unknown, notNull.Nullmeans "this value does not exist" whileUnknownmeans "not yet determined." Downstream consumers treatNullas a real value during planning.
Linter:
unknownguard— flags data source Read methods missing unknown input checks.
API Response Validation
Validate both status code and parsed response body:
if apiResp.StatusCode() != 200 || apiResp.JSON200 == nil {
resp.Diagnostics.AddError(
"Error Reading Resource",
fmt.Sprintf("status: %d, body: %s", apiResp.StatusCode(), string(apiResp.Body)),
)
return
}
The generated client only populates JSON200 when the response Content-Type is JSON.
Computed-Only List Attributes
Always return an empty list [] instead of null for computed-only list attributes. Returning null causes errors when consumers iterate (e.g., for_each, for expressions):
// CORRECT — empty list is safely iterable
emptyList, diags := types.ListValueFrom(ctx, elemType, []MyValueType{})
data.Items = emptyList
// WRONG — causes "Iteration over null value" errors
data.Items = types.ListNull(elemType)
Step 5: Register the Data Source
Add to provider.go in the DataSources() method.
Step 6: Add Tests and Examples
- Test file:
internal/provider/<name>_data_source_test.go - Example:
examples/data-sources/doit_<name>/data-source.tf - Add test env vars to
.envrc.local - Always
terraform applyevery example at least once to verify
Step 7: Generate Docs
make docs
List Data Sources (Plural Endpoints)
List data sources (e.g., doit_budgets, doit_allocations) return arrays of items.
Smart Pagination
List data sources support smart pagination — they honor user-provided values when set, otherwise auto-paginate:
| User Sets | Behavior |
|---|---|
Neither max_results nor page_token | Auto-paginate: fetch all pages |
max_results only | Single page: return up to N items |
page_token only | Auto-paginate from token |
| Both | Manual pagination |
userControlsPagination := !data.MaxResults.IsNull() && !data.MaxResults.IsUnknown()
if userControlsPagination {
params.MaxResults = new(data.MaxResults.ValueString())
// Single API call, preserve page_token from response
} else {
// Auto mode: fetch all pages
var allItems []models.ItemType
for {
apiResp, _ := d.client.ListItemsWithResponse(ctx, params)
allItems = append(allItems, *apiResp.JSON200.Items...)
if apiResp.JSON200.PageToken == nil || *apiResp.JSON200.PageToken == "" {
break
}
params.PageToken = apiResp.JSON200.PageToken
}
}
Key principle: User-provided values MUST be preserved in state to prevent perpetual diffs.
Mapping API Types to Generated Values
Use NewXxxValue() constructors with map[string]attr.Value{}:
budgetVal, diags := datasource_budgets.NewBudgetsValue(
datasource_budgets.BudgetsValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"id": types.StringPointerValue(budget.Id),
"budget_name": types.StringPointerValue(budget.BudgetName),
"amount": types.Float64PointerValue(budget.Amount),
},
)
Type Handling Tips
- Pointer vs Non-Pointer: Check
models_gen.go— pointer (*string) →types.StringPointerValue(), non-pointer →types.StringValue() - Nullable fields:
nullable.Nullable[T]→ wrap withnullableToPointer()before passing totypes.XxxPointerValue(). Example:types.StringPointerValue(nullableToPointer(resp.Field)) - Enum Types: Convert to string first —
types.StringValue(string(s.Mode)) - Nested Lists: Create helper functions to map nested structures
RowCount Determinism
Always guarantee a deterministic fallback by calculating slice length when the API omits rowCount:
if apiResp.JSON200.RowCount != nil {
data.RowCount = types.Int64Value(*apiResp.JSON200.RowCount)
} else {
data.RowCount = types.Int64Value(int64(len(items)))
}
Common Mistake
Generated schema field names match the API's JSON tags in snake_case, NOT arbitrary Terraform conventions. Verify names against datasource_xxx/xxx_data_source_gen.go and models/models_gen.go.
When not to use it
- →When writing data source code from scratch without scaffolding
- →When returning `Null` instead of `Unknown` for undetermined values
- →When returning `null` for computed-only list attributes
Limitations
- →Requires using the scaffold command
- →Requires checking for unknown inputs before making API calls
- →Requires returning an empty list `[]` instead of `null` for computed-only list attributes
How it compares
This skill provides a structured, step-by-step process for implementing Terraform data sources, ensuring adherence to best practices like scaffolding, unknown input handling, and API response validation, unlike manual implementation.
Compared to similar skills
implement-datasource side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| implement-datasource (this skill) | 0 | 11d | Review | Advanced |
| new-terraform-provider | 0 | 3mo | No flags | Beginner |
| aws-solution-architect | 20 | 2mo | Review | Advanced |
| terraform-module-library | 7 | 4mo | No flags | Advanced |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
new-terraform-provider
hashicorp
Use this when scaffolding a new Terraform provider.
aws-solution-architect
alirezarezvani
Design AWS architectures for startups using serverless patterns and IaC templates. Use when asked to design serverless architecture, create CloudFormation templates, optimize AWS costs, set up CI/CD pipelines, or migrate to AWS. Covers Lambda, API Gateway, DynamoDB, ECS, Aurora, and cost optimization.
terraform-module-library
wshobson
Build reusable Terraform modules for AWS, Azure, and GCP infrastructure following infrastructure-as-code best practices. Use when creating infrastructure modules, standardizing cloud provisioning, or implementing reusable IaC components.
azure-deployment-preflight
github
Performs comprehensive preflight validation of Bicep deployments to Azure, including template syntax validation, what-if analysis, and permission checks. Use this skill before any deployment to Azure to preview changes, identify potential issues, and ensure the deployment will succeed. Activate when users mention deploying to Azure, validating Bicep files, checking deployment permissions, previewing infrastructure changes, running what-if, or preparing for azd provision.
terraform-azurerm-set-diff-analyzer
github
Analyze Terraform plan JSON output for AzureRM Provider to distinguish between false-positive diffs (order-only changes in Set-type attributes) and actual resource changes. Use when reviewing terraform plan output for Azure resources like Application Gateway, Load Balancer, Firewall, Front Door, NSG, and other resources with Set-type attributes that cause spurious diffs due to internal ordering changes.
terraform-skill
sickn33
Terraform infrastructure as code best practices