Add user_agent_extra provider configuration attribute - #5863
Conversation
Adds a `user_agent_extra` attribute to the provider block, equivalent to the existing DATABRICKS_USER_AGENT_EXTRA environment variable (databricks#3520). This lets Terraform modules built on top of the provider (e.g. terraform-databricks-sra) ship usage attribution in their provider configuration without requiring users to set environment variables, which are bypassed when terraform is invoked directly or via CI/CD. The product-string parsing moves from internal/providers/sdkv2 to internal/providers/common so the plugin framework provider can reuse it (pluginfw cannot import sdkv2). The attribute is added to both muxed provider schemas and applied at configure time via useragent.WithUserAgentExtra, which deduplicates identical key/value pairs, so double application from the two muxed providers or from env var + attribute is safe. Co-authored-by: Isaac
Co-authored-by: Isaac
3686e17 to
98008f7
Compare
alexott
left a comment
There was a problem hiding this comment.
Code review
Found 1 issue:
user_agent_extrais a provider configuration attribute, but both provider implementations apply it throughuseragent.WithUserAgentExtra, which the SDK documents as per-process state. If a Terraform run configures two Databricks provider instances or aliases with differentuser_agent_extravalues, whichever provider configures first permanently registers its product for the whole plugin process, so requests from the other provider will also carry that module attribution. This makes the attribute behave differently from normal provider config and can misattribute requests across modules/workspaces. The existing context-based user-agent dimensions avoid this by adding data to the request context; this new value needs a provider/client-scoped path instead of the global registry.
terraform-provider-databricks/internal/providers/pluginfw/pluginfw.go
Lines 201 to 205 in 98008f7
terraform-provider-databricks/internal/providers/sdkv2/sdkv2.go
Lines 183 to 188 in 98008f7
Generated with Codex.
Terraform runs a separate plugin process per provider configuration and sends a single Configure call per process, so configure-time extras are effectively scoped to their provider configuration despite the registry being process-global. Verified empirically with two aliased providers against a local capture server: each alias's requests carry only its own user_agent_extra products. Co-authored-by: Isaac
|
Terraform runs a separate plugin process per provider configuration and sends a single Configure call per process, so configure-time extras are effectively scoped to their provider configuration despite the registry being process-global. Verified empirically with two aliased providers against a local capture server: each alias's requests carry only its own user_agent_extra products. @alexott |
Re: per-process user-agent state concernA code-review finding flagged that What's accurate: What doesn't hold — the shared-process premise: I tested this empirically rather than reasoning from theory. I built the provider from this branch, dev-override'd it into Terraform v1.14.0, and ran a Reproduction harness
import http.server, json
class H(http.server.BaseHTTPRequestHandler):
def log_message(self, *a): pass
def do_GET(self):
with open('ua.log', 'a') as f:
f.write(self.path + "\t" + self.headers.get('User-Agent', '?') + "\n")
body = json.dumps({"userName": "test@example.com"}).encode()
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
self.wfile.write(body)
http.server.HTTPServer(('127.0.0.1', 18080), H).serve_forever()
terraform {
required_providers {
databricks = { source = "databricks/databricks" }
}
}
provider "databricks" {
alias = "one"
host = "http://127.0.0.1:18080"
token = "fake-token-one"
user_agent_extra = "module-one/1.1.1"
}
provider "databricks" {
alias = "two"
host = "http://127.0.0.1:18080"
token = "fake-token-two"
user_agent_extra = "module-two/2.2.2"
}
data "databricks_current_user" "one" { provider = databricks.one }
data "databricks_current_user" "two" { provider = databricks.two }
provider_installation {
dev_overrides {
"databricks/databricks" = "/tmp/tfdev"
}
direct {}
}Run: go build -o /tmp/tfdev/terraform-provider-databricks .
python3 server.py &
TF_CLI_CONFIG_FILE=$PWD/tfrc TF_LOG=DEBUG terraform plan -input=falseCaptured Zero cross-contamination across the captured requests. The Why not a client-scoped path: I evaluated it. The only post-header-stamp hook is wrapping Residual caveat (real but narrow): in-process embeddings that configure multiple provider instances in one process — e.g. acceptance tests using provider factories — do share the registry. Same value → deduped by the SDK; different values → both appear. Changes made in response: documented the scoping guarantee and the caveat in the |
…a-provider-attribute
Unit testsIf this PR is from a fork, the If this PR changes Actions -> Warm Go Cache -> Run workflow -> pr_number = 5863 Re-run the failed check once the cache warming completes. Integration testsIntegration tests don't run automatically for external contributors; an authorized user can run them manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
Changes
Adds a
user_agent_extraattribute to the provider block, equivalent to the existingDATABRICKS_USER_AGENT_EXTRAenvironment variable introduced in #3520:Motivation
Terraform modules built on top of the provider (e.g. terraform-databricks-sra) want to ship usage attribution in their provider configuration. The environment variable works, but it must be set by whoever invokes Terraform — module authors can't set it on behalf of their users, so attribution silently disappears when users run
terraformdirectly or via CI/CD, HCP Terraform, Spacelift, etc. A provider-block attribute travels with the module'sprovider.tfand requires no user action.Implementation
The product-string parsing (
ParseUserAgentExtra, RFC 9110 validation) moves unchanged frominternal/providers/sdkv2tointernal/providers/commonso the plugin framework provider can reuse it (pluginfwcannot importsdkv2), alongside a newApplyUserAgentExtrahelper shared by the env-var path and both configure paths.The attribute is added to both muxed provider schemas (SDKv2 and plugin framework) to keep the mux schemas identical, and applied at configure time via
useragent.WithUserAgentExtra.The SDK deduplicates identical key/value pairs, so double application — from the two muxed providers being configured in the same process, or from the env var and the attribute both being set — is safe. Semantics (parsing, validation, error messages) are identical to the env var.
Per-configuration scoping (verified empirically): although
useragent.WithUserAgentExtrais process-global, Terraform runs a separate plugin process per provider configuration and the plugin protocol sends a singleConfigureper process, so extras are effectively scoped to the provider configuration that set them. Verified with Terraform v1.14.0 against a local HTTP capture server using two aliased providers with differentuser_agent_extravalues — every request carried exactly its own alias's product and never the other's:The only same-process double application is the mux fan-out of
Configureto the SDKv2 and plugin framework servers (same value), which the SDK's exact key/value dedupe covers. In-process embeddings that configure multiple provider instances in one process (e.g. acceptance tests using provider factories) share the registry; a per-client mechanism would require SDK support (config.Confighas no per-client user agent hook, and wrappingHTTPTransportwould bypass the SDK's default transport andskip_verifyhandling).Invalid product strings surface as a configure-time diagnostic instead of the env-var path's panic.
Tests
New test cases in
sdkv2'sTestConfigureDatabricksClientandpluginfw'sTestConfigureverifying the attribute lands in the User-Agent, plus invalid-input tests asserting a configure-time error in both providers.New
TestMuxedProviderSchemaIncludesUserAgentExtraverifying the muxed provider serves a consistent schema including the new attribute.Existing
Test_ParseUserAgentExtraupdated to the moved package; coverage unchanged.make lint(staticcheck) andgo test -short ./...pass locally (all 56 packages).make testrun locallyrelevant change in
docs/foldercovered with integration tests in
internal/acceptanceusing Go SDK
using TF Plugin Framework
has entry in
NEXT_CHANGELOG.mdfileThis pull request and its description were written by Isaac.