Identify Pulumi in the Cloudflare User-Agent - #1656
Open
jkodroff wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
✅ No issues found
About Unblocked
Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.
📖 Documentation — Learn more in our docs.
💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.
👍 Give feedback — React to comments with 👍 or 👎 to help us improve.
⚙️ Customize — Adjust settings in your preferences.
Does the PR have any schema changes?Generated by schema-tools v0.8.1. Looking good! No breaking changes found. Maintainer note: consult the runbook for dealing with any breaking changes. |
Cloudflare could not attribute API traffic to Pulumi, so provider usage looked like plain Terraform usage. Upstream built the hook for us back in 2023 (cloudflare/terraform-provider-cloudflare#2831) - a provider config attribute `user_agent_operator_suffix` that replaces the `terraform/<version>` token - but the Pulumi side was never wired up. Default `userAgentOperatorSuffix` to `pulumi/<version>` from a PreConfigureCallback. Requests now go out as: terraform-provider-cloudflare/pulumi terraform-plugin-framework/<v> pulumi/<v> Precedence is explicit config, then CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIX, then the default. An explicitly empty value in either removes the key, which opts out by restoring upstream's `terraform/<version>` default - so this needs no new config surface. Setting the suffix from a callback rather than `Config[...].Default` keeps the version out of the generated schema. `Default.Value` is copied verbatim into schema.json and every SDK as a client-side literal, which would churn the schema on every release and leave an SDK reporting whichever version it was built at. Two upstream bugs sit in this path, both still present on main and now tracked in cloudflare/terraform-provider-cloudflare#7323: - `Configure` formats the suffix with `types.String.String()`, which is `fmt.Sprintf("%q", ...)`, so it would go out double-quoted. - The `IsNull()` guard does not cover `IsUnknown()`, so an unknown value would go out as the literal `<unknown>`. patch 0002 fixes both and is written to be upstreamable as-is. patch 0003 is Pulumi-specific: it rewrites the config description, which would otherwise have claimed the default is the Terraform version, and adds the option to upstream's docs/index.md, which omitted it entirely. The shim still passes the literal string "pulumi" where upstream expects a semver. That is pre-existing, it also feeds x-stainless-package-version, and every obvious fix has a downside - see #1655. Fixes #179 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jkodroff
force-pushed
the
jkodroff/user-agent-string
branch
from
August 24, 2026 22:01
07a1a5f to
0d151c6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #179
Cloudflare couldn't attribute API traffic to Pulumi, so our usage looked like plain Terraform usage. Upstream built the hook for us back in 2023 (cloudflare/terraform-provider-cloudflare#2831, filed by @jaxxstorm) — a provider config attribute
user_agent_operator_suffixthat replaces theterraform/<version>token in the User-Agent. It shipped; the Pulumi side was never wired up. That's why @pierskarsenbarg's 2024 screenshot on #179 still showed Terraform.This defaults
userAgentOperatorSuffixtopulumi/<version>.What goes out on the wire
Verified by
TestUserAgentHeader, which stands up anhttptestserver and reads the actual header:User-Agentterraform-provider-cloudflare/pulumi terraform-plugin-framework/… pulumi/6.0.0userAgentOperatorSuffix: "mycorp/1.0"… mycorp/1.0userAgentOperatorSuffix: ""… terraform/1.0.0+pulumi-terraform-bridgePrecedence is explicit config →
CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIX→pulumi/<version>. An explicitly empty value in either removes the key, which opts out by restoring upstream's default — so the opt-out #179 asks for needs no new config surface.Why
PreConfigureCallbackand notConfig[...].DefaulttfgencopiesDefault.Valueverbatim intoschema.jsonand from there into every SDK as a client-side literal. That would churn the schema on every release, bake6.0.0-alpha.0+devin from a localmake tfgen, and leave an SDK built at 6.13.0 still reportingpulumi/6.13.0against a 6.20.0 plugin. It also can't express the env-var precedence or the empty-string opt-out.One caveat worth knowing: the bridge skips
PreConfigureCallbackentirely when provider config contains unknowns, in which case no suffix is injected and upstream falls back toterraform/<version>. Noted in the code.Upstream bugs
Two bugs sit directly in this code path, both still present on upstream
main. I filed cloudflare/terraform-provider-cloudflare#7323:Configureformats the suffix withtypes.String.String(), which isfmt.Sprintf("%q", …)— so the suffix would have gone out double-quoted.IsNull()guard doesn't coverIsUnknown(), so an unknown value would go out as the literal<unknown>.patches/0002fixes both and is written to be upstreamable as-is; it can be dropped once #7323 lands. Theassert.NotContains(t, userAgent, "\"")assertion in the tests is the regression guard.Docs
patches/0003is deliberately not upstreamable. The existing config description would have become actively wrong — it claims setting this value "will remove the Terraform version from the HTTP User Agent string", implying the default is the Terraform version, when under Pulumi it's nowpulumi/<version>. It also leaked the word "Terraform" into our schema (the bridge's find/replace fixes the markdown but not the schema string).The same patch adds the option to upstream's
docs/index.md, which omitteduser_agent_operator_suffixentirely — that's what regeneratesdocs/_index.md, so it stays generated rather than hand-edited.README.mdis hand-maintained and updated to match.Worth noting: across the org, nobody really documents this. azure-native is the only provider that documents its knob at all, and there's no prose anywhere on pulumi.com explaining that we identify ourselves to a cloud vendor or how to turn it off. This makes Cloudflare the best-documented of the bunch, more or less by accident of upstream having a schema description at all.
Testing
TestSetUserAgentOperatorSuffix— six precedence cases (unset, config-set, config-set-empty, env-set, env-set-empty, config-beats-env).TestCheckConfigSetsUserAgentOperatorSuffix— proves the bridge actually invokes the callback and returns the mutation.TestUserAgentHeader— the three wire-level cases in the table above, modelled onpulumi-aws/provider/provider_endpoint_test.go.make lintclean,go test -shortgreen,make tfgen/make build_sdks/make build_registry_docsregenerated. The SDK diff is 14 files and is entirely the description string.Follow-up
#1655 — the shim passes the literal string
"pulumi"where upstream expects a semver, which also poisonsx-stainless-package-version. Pre-existing and left alone here; the issue lays out why each obvious fix is worse than it looks.🤖 Generated with Claude Code