hashicorp / hashicorp/terraform-plugin-sdk

Consider Supporting Resource Pre-Logic Context Hooks

Open
#858 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.