Skip to content

Identify Pulumi in the Cloudflare User-Agent - #1656

Open
jkodroff wants to merge 1 commit into
masterfrom
jkodroff/user-agent-string
Open

Identify Pulumi in the Cloudflare User-Agent#1656
jkodroff wants to merge 1 commit into
masterfrom
jkodroff/user-agent-string

Conversation

@jkodroff

Copy link
Copy Markdown
Member

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_suffix that replaces the terraform/<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 userAgentOperatorSuffix to pulumi/<version>.

What goes out on the wire

Verified by TestUserAgentHeader, which stands up an httptest server and reads the actual header:

Config User-Agent
default terraform-provider-cloudflare/pulumi terraform-plugin-framework/… pulumi/6.0.0
userAgentOperatorSuffix: "mycorp/1.0" … mycorp/1.0
userAgentOperatorSuffix: "" … terraform/1.0.0+pulumi-terraform-bridge

Precedence is explicit config → CLOUDFLARE_USER_AGENT_OPERATOR_SUFFIXpulumi/<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 PreConfigureCallback and not Config[...].Default

tfgen copies Default.Value verbatim into schema.json and from there into every SDK as a client-side literal. That would churn the schema on every release, bake 6.0.0-alpha.0+dev in from a local make tfgen, and leave an SDK built at 6.13.0 still reporting pulumi/6.13.0 against 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 PreConfigureCallback entirely when provider config contains unknowns, in which case no suffix is injected and upstream falls back to terraform/<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:

  • Configure formats the suffix with types.String.String(), which is fmt.Sprintf("%q", …) — so the suffix would have gone out double-quoted.
  • The IsNull() guard doesn't cover IsUnknown(), so an unknown value would go out as the literal <unknown>.

patches/0002 fixes both and is written to be upstreamable as-is; it can be dropped once #7323 lands. The assert.NotContains(t, userAgent, "\"") assertion in the tests is the regression guard.

Docs

patches/0003 is 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 now pulumi/<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 omitted user_agent_operator_suffix entirely — that's what regenerates docs/_index.md, so it stays generated rather than hand-edited. README.md is 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 on pulumi-aws/provider/provider_endpoint_test.go.

make lint clean, go test -short green, make tfgen / make build_sdks / make build_registry_docs regenerated. 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 poisons x-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

@jkodroff
jkodroff requested a review from corymhall August 19, 2026 23:40

@unblocked unblocked Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@github-actions

Copy link
Copy Markdown

Does the PR have any schema changes?

Generated by schema-tools v0.8.1.

Looking good! No breaking changes found.
No new resources/functions/types.

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
jkodroff force-pushed the jkodroff/user-agent-string branch from 07a1a5f to 0d151c6 Compare August 24, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Include Pulumi in UserAgent

1 participant