hashicorp / hashicorp/terraform-plugin-sdk
Consider Supporting Resource Pre-Logic Context Hooks
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 485
- Forks
- 244
- Avg merge
- 19h 57m
- Merged PRs (30d)
- 4
Description
SDK version
v2.10.1
Use-cases
When attempting to setup a provider with terraform-plugin-log/tflog, trying to add variables via tflog.With() or use subsystems requires each logging context to be explicitly built up in a lot of logic:
- ApplyResourceChange RPC -> Context Handling -> Create/Update/Delete
- ImportResourceState RPC -> Context Handling -> Importer
- PlanResourceChange RPC -> Context Handling -> CustomizeDiff
- ReadDataSource/ReadResource RPCs -> Context Handling -> Read
- UpgradeResourceState RPC -> Context Handling -> StateUpgraders
- ValidateDataSourceConfig/ValidateResourceConfig RPCs -> Context Handling -> Schema
Currently the setup must be done within each and every one of those provider defined pieces, which could be confusing and/or error prone.
Attempted Solutions
Adding a helper function, which gets invoked within every CRUD, CustomizeDiff, etc. function. e.g.
func setupLoggingContext(ctx context.Context) context.Context {
ctx = tflog.With(ctx, "example_key", "example value")
return ctx
}
func exampleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
ctx = setupLoggingContext(ctx)
// ...
}
func exampleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
ctx = setupLoggingContext(ctx)
// ...
}
func exampleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
ctx = setupLoggingContext(ctx)
// ...
}
func exampleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
ctx = setupLoggingContext(ctx)
// ...
}
func exampleCustomizeDiff(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) diag.Diagnostics {
ctx = setupLoggingContext(ctx)
// ...
}
Proposal
The SDK could offer a Resource level field, that serves as a hook before all these various logic points are invoked, e.g.
type Resource struct {
// ... existing bits ...
// ContextFunc is a hook which can be used to instantiate details inside the context before it is passed into Resource logic.
ContextFunc func(context.Context) context.Context
}
It might also be good to have the meta passed in there as well so you could do things like important provider level pieces of information (location, account information, etc.) via With() or SubsystemWith(), although this makes for a little bit more complicated provider logic because it will need to check that the provider is configured first.
// SDK logic
type Resource struct {
// ... existing bits ...
// ContextFunc is a hook which can be used to instantiate details inside the context before it is passed into Resource logic.
ContextFunc func(context.Context, interface{}) context.Context
}
// Provider logic
func setupLoggingContext(ctx context.Context, meta interface{}) context.Context {
ctx = tfsdk.NewSubsystem(ctx, /* ... */)
// Provider may not have been configured yet.
if meta != nil {
client, ok := meta.(ActualType)
if !ok {
return ctx
}
ctx = tfsdk.With(ctx, "account_id", client.accountID)
ctx = tfsdk.With(ctx, "region", client.region)
}
return ctx
}
When defined on a resource, this logic would be invoked automatically prior to executing the provider defined Create, etc. functions.
One thing that isn't immediately clear is whether this should be split up per RPC/SDK functionality per resource, left as a monolithic configuration per resource, or potentially offered at an even higher level (e.g. Provider). Being monolithic at the resource level seems to be the most pragmatic choice given how providers are typically written with "API service" boundaries that may dictate needing multiple configurations across the provider. This does however prevent providing these hooks with "rich" information like ResourceData or ResourceDiff since that is dependent on the RPC.
References
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the Resource lifecycle entry points listed in the issue, including ApplyResourceChange, ImportResourceState, PlanResourceChange, ReadResource, UpgradeResourceState, and validation RPCs. Compare how context reaches Create, Read, Update, Delete, CustomizeDiff, StateUpgraders, and Schema logic, then determine whether a Resource-level ContextFunc or a different hook scope covers the stated use cases. Done means the hook design and its behavior across these paths are specified.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend-api-design, devtools
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 30/100