Provider Resources Implementation
References: Plugin Framework · Resources · Data Sources
File Structure
internal/service/<service>/
├── <resource_name>.go # Resource implementation
├── <resource_name>_test.go # Acceptance tests
├── <resource_name>_data_source.go # Data source (if applicable)
├── find.go # Finder functions
├── exports_test.go # Test exports
└── service_package_gen.go # Auto-generated registration
website/docs/r/<service>_<resource_name>.html.markdown # Resource docs
website/docs/d/<service>_<resource_name>.html.markdown # Data source docs
Resource Structure
Plugin Framework Pattern (preferred)
type resourceExample struct {
framework.ResourceWithConfigure
}
func (r *resourceExample) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_example"
}
func (r *resourceExample) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": framework.IDAttribute(),
"name": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
Validators: []validator.String{
stringvalidator.LengthBetween(1, 255),
},
},
"arn": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}
SDKv2 Pattern (legacy — use only when extending existing SDKv2 resources)
func ResourceExample() *schema.Resource {
return &schema.Resource{
CreateWithoutTimeout: resourceExampleCreate,
ReadWithoutTimeout: resourceExampleRead,
UpdateWithoutTimeout: resourceExampleUpdate,
DeleteWithoutTimeout: resourceExampleDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
Schema: map[string]*schema.Schema{
// ...
},
CustomizeDiff: verify.SetTagsDiff,
}
}
CRUD Operations
Each resource implements Create, Read, Update, and Delete methods.
Key patterns:
- Create: deserialize plan → call API → set ID and computed fields → save state
- Read: load state → call finder → handle NotFound by removing from state → update state
- Update: compare plan vs state → call API for changed fields → save state
- Delete: load state → call API → handle NotFound gracefully
See references/crud-operations.md for complete code examples.
Schema Design
Attribute Types
| Terraform Type | Framework Type | Use Case |
|---|---|---|
string |
schema.StringAttribute |
Names, ARNs, IDs |
number |
schema.Int64Attribute, schema.Float64Attribute |
Counts, sizes |
bool |
schema.BoolAttribute |
Feature flags |
list |
schema.ListAttribute |
Ordered collections |
set |
schema.SetAttribute |
Unordered unique items |
map |
schema.MapAttribute |
Key-value pairs |
object |
schema.SingleNestedAttribute |
Complex nested config |
Plan Modifiers
stringplanmodifier.RequiresReplace() // Force replacement when value changes
stringplanmodifier.UseStateForUnknown() // Preserve computed value during plan
// Custom plan modifier
stringplanmodifier.RequiresReplaceIf(
func(ctx context.Context, req planmodifier.StringRequest, resp *stringplanmodifier.RequiresReplaceIfFuncResponse) {
// Custom logic
},
"description",
"markdown description",
)
Validators
// String
stringvalidator.LengthBetween(1, 255)
stringvalidator.RegexMatches(regexp.MustCompile(`^[a-z0-9-]+$`), "must be lowercase alphanumeric with hyphens")
stringvalidator.OneOf("option1", "option2", "option3")
// Int64
int64validator.Between(1, 100)
int64validator.AtLeast(1)
int64validator.AtMost(1000)
// List
listvalidator.SizeAtLeast(1)
listvalidator.SizeAtMost(10)
Sensitive Attributes
Mark attributes containing secrets as Sensitive: true to prevent values from appearing in logs and plan output.
"password": schema.StringAttribute{
Required: true,
Sensitive: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(8),
},
}
State Management
Finder Functions
Wrap API Get calls in a finder that returns retry.NotFoundError when the resource is missing, enabling consistent NotFound handling across CRUD operations.
State Waiters
For async resources, use retry.StateChangeConf to poll until the resource reaches a target state.
See references/state-management.md for implementation examples.
Error Handling
API Error Matching
var notFound *types.ResourceNotFoundException
if errors.As(err, ¬Found) { /* resource missing */ }
var conflict *types.ConflictException
if errors.As(err, &conflict) { /* state conflict */ }
var throttle *types.ThrottlingException
if errors.As(err, &throttle) { /* rate limited — SDK handles retry */ }
Diagnostics
resp.Diagnostics.AddError("Error creating resource",
fmt.Sprintf("Could not create resource: %s", err))
resp.Diagnostics.AddWarning("Resource modified outside Terraform",
"State may be inconsistent")
resp.Diagnostics.AddAttributeError(path.Root("name"),
"Invalid name", "Name must be lowercase alphanumeric")
Testing
Run acceptance tests with TF_ACC=1:
go test -c -o /dev/null ./internal/service/<service> # compile check
TF_ACC=1 go test ./internal/service/<service> -run TestAccExample -v -timeout 60m
TF_ACC=1 go test ./internal/service/<service> -sweep=<region> -v # cleanup
Every resource needs:
- Basic acceptance test with import state verification
- Disappears test (
acctest.CheckResourceDisappears) testAccCheckExampleExistsandtestAccCheckExampleDestroyhelpers
See references/testing-examples.md for full test code.
Documentation
Each resource and data source requires an HTML markdown doc with: Example Usage, Argument Reference, Attribute Reference, Import.
See references/documentation-template.md for the standard template.
Pre-Submission Checklist
- Code compiles without errors
- All tests pass locally
- All CRUD operations implemented
- Import implemented and tested
- Disappears test included
- Documentation complete with examples
- Error messages are clear and actionable
- Sensitive attributes marked
- Plan modifiers set appropriately
- Validators cover edge cases