All skills
pulumi avatar

/pulumi-upgrade-provider

@8880e3c official
by pulumipulumi/agent-skills70 stars
6

Automate Pulumi provider repo upgrades with the `upgrade-provider` tool. Use when upgrading a pulumi provider repository to a new upstream version, running `upgrade-provider`, and addressing its common failure modes like patch conflicts or missing module mappings.

Use this Skill: https://skilld.dev/gh/pulumi/agent-skills/pulumi-upgrade-provider

This session only. Nothing lands on disk.

referencesupgrade-provider-errors.md

≈5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Upgrade Provider Errors

Use this file when the upgrade-provider tool fails and you need concrete fixes. For patch edits/removals/rebases, follow the upstream-patches skill workflow. Preserve --no-submit on every upgrade-provider retry.

Patched upstream has interrupted or unexpected Git state

The tool deliberately leaves active Git operations, pulumi/patch-checkout, and unexpected branches unchanged because it cannot prove that an interrupted checkout applied the complete patch stack.

Preserve work by default:

  1. Inspect git -C upstream status and identify whether git am, rebase, merge, cherry-pick, or another operation is active.
  2. Use skill upstream-patches for conflict resolution or patch edits.
  3. Complete or safely abort the active operation. If checkout was interrupted during git am, verify that every patches/*.patch was applied; later patch files may not have been reached.
  4. Ensure the patch stack was rebased onto the requested target. If needed, run the target rebase from the provider root and resolve it fully:
./scripts/upstream.sh rebase -o refs/tags/v<TARGET>
  1. Once every patch is applied and the target rebase is complete, write patches back and exit checkout mode:
./scripts/upstream.sh check_in
  1. Confirm upstream is no longer on pulumi/patch-checkout, then rerun upgrade-provider with --no-submit.

Only when intentionally discarding all interrupted patch work may you run:

./scripts/upstream.sh init -f

This deletes active operation state and patch-checkout work and can clean untracked files. Treat it as destructive discard, not normal recovery.

Patch conflicts during rebase

The tool uses scripts/upstream.sh to apply patch commits in the upstream submodule. If a patch no longer applies cleanly, you will see rebase errors like:

error: could not apply 83b04967e... docs patching
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

Fix from the upstream directory, following upstream-patches defaults (edit the owning patch commit; do not create a new patch unless asked):

  1. Identify conflicted files.
  2. Resolve conflicts while preserving the intent of the patch in patches/.
  3. Search for conflict markers and remove all of them before continuing.
  4. git add the resolved files.
  5. git rebase --continue.
  6. Repeat until the rebase is complete, then return to the provider root and exit checkout mode:
./scripts/upstream.sh check_in
  1. Confirm upstream is detached rather than on pulumi/patch-checkout, then rerun upgrade-provider with --no-submit.

If git rebase --continue opens an editor in automation contexts, run GIT_EDITOR=true git rebase --continue.

Avoid:

  • Rerunning the tool before check_in; safe patched-provider preflight will reject leftover checkout state.
  • Hand-editing patches/*.patch unless intentionally doing raw patch surgery.
  • Direct edits under upstream/ outside checkout/check_in workflow.

Patch intent guidance

  • Docs-related patches usually replace or remove Terraform references. Preserve those changes when resolving conflicts.
  • A small, single-purpose docs patch that only removes an untranslatable section — for example an ## Import section written in Terraform-only import syntax — can sometimes be retired entirely and replaced with a targeted docs edit rule in provider/resources.go rather than resolving the conflict. Add the rule to ProviderInfo.DocRules.EditRules (e.g. a SkipSectionByHeader-style rule that drops the section by its header); verify the exact helper against the pulumi-terraform-bridge version in provider/go.mod. This avoids re-resolving the same conflict on every future upgrade, and when the patch was the repo's only one it can also remove the need for the upstream submodule (delete the .gitmodules entry).
  • Do not treat this as a default conflict-resolution tactic. Wholesale-replacing a docs patch with an edit rule only works when the patch is small and its intent maps cleanly onto a small rule. Patches that span multiple files or perform substantive rewrites should have their conflict resolved in place (per the guidance above), not be converted. Keep docs edit rules small and targeted — if reproducing the patch would require a large or brittle rule, resolve the conflict instead of converting it.

Upstream migrated from SDKv2 to Plugin Framework

Treat an upstream Terraform Plugin SDKv2-to-Plugin-Framework migration as a known migration path, not by itself as an architectural blocker. Common signals include:

  • Compiler errors such as undefined: <package>.Provider or code that still expects *schema.Provider.
  • Upstream go.mod dropping or making terraform-plugin-sdk/v2 indirect while adding terraform-plugin-framework.
  • Upstream release notes or commits that announce a Plugin Framework migration or SDKv2 removal.
  • make tfgen or upgrade-provider errors that a token mapping in provider/resources.go references a resource that is no longer present in the provider, and suggests removing that mapping. An unmuxed SDKv2 bridge (shimv2.NewProvider) can only see resources registered in the upstream SDKv2 ResourcesMap; a resource that upstream moved to the Plugin Framework is absent from the shimmed map, so its existing mapping looks stale. Do not remove the mapping on the tool's suggestion — that silently drops a resource users depend on. See "A mapped resource is reported missing" below.

Read the current bridge guide before editing provider entry points or imports:

Do not rely on remembered import paths or API signatures. The guides describe the current bridge release; verify the packages and APIs against the pulumi-terraform-bridge version selected by provider/go.mod before applying the migration.

First look for a public upstream package that constructs the Plugin Framework provider. If upstream exposes the constructor only from an unimportable internal/ package, use skill upstream-patches to add the smallest non-internal shim that returns the upstream provider. This is a legitimate new-patch case; keep the patch limited to exposing the constructor and document when it can be removed.

After applying the appropriate guide and any required patch, run focused provider builds or tests, then rerun upgrade-provider with --no-submit.

A mapped resource is reported missing (do not remove the mapping)

When tfgen finds a token mapping whose resource is no longer in the shimmed provider, it reports the resource as missing and suggests removing the mapping:

Pulumi token "<pulumi-token>" is mapped to TF provider resource "<tf_resource>", but no such resource found. Remove the mapping and try again

Before removing the mapping, verify the removal was real. The same "missing" error appears whether upstream deleted the resource or moved it to the Plugin Framework — an unmuxed SDKv2 bridge can't see a Framework resource — so confirm which happened:

  1. Inspect the upstream provider at the target tag. If the resource is gone from the SDKv2 ResourcesMap but a Plugin Framework equivalent now exists (a resource.Resource implementation registered in the framework provider's Resources method), it was migrated, not removed.
  2. Read the upstream CHANGELOG / release notes for the target version. A genuine removal or deprecation is normally announced; a Plugin Framework migration is often described only as an internal refactor.
  3. Confirm the token really left the generated schema by diffing against the default branch:
default_branch=$(git remote show origin | sed -n 's/.*HEAD branch: //p')
schema_path=$(find provider/cmd -path '*/schema.json' -print -quit)
diff <(git show "origin/${default_branch}:${schema_path}" | jq -r '.resources | keys[]') \
     <(jq -r '.resources | keys[]' "$schema_path")

Only if it was intentionally removed upstream should you drop the token mapping — and then call out the removal in the upgrade PR so reviewers can plan a deprecation. If it was migrated to the Plugin Framework, keep the resource by muxing the provider (follow the mux guide linked above) so the bridge serves it again; do not remove the mapping.

Upstream provider relies on ignored replace directives

When upgrade-provider fails during Update TF Provider with an upstream module resolution error like:

go get github.com/rancher/...: exit status 1:
go: github.com/rancher/terraform-provider-rancher2@... requires github.com/rancher/rancher@v0.0.0: unknown revision v0.0.0

the upstream provider may have invalid-looking require entries that are only made valid by its own replace directives. Go ignores replace directives from dependency modules; only the main module's go.mod replacements are honored.

Fix in the Pulumi provider repo:

  1. Inspect upstream go.mod at the target tag or commit.
  2. Add the narrowest necessary upstream replace directives to provider/go.mod.
  3. Avoid copying the entire upstream replace block unless required; broad replacements can conflict with Pulumi or bridge dependencies.
  4. Run go mod tidy from provider/.
  5. Rerun upgrade-provider from the repo root with the target version explicit. Preserve the original major/non-major intent:
upgrade-provider pulumi/<provider> --repo-path . --no-submit --target-version <version>

Add --major only when the target upstream version crosses the current upstream major version. Passing --major for a same-major target makes upgrade-provider fail and can trigger unwanted major-version rewrite behavior.

If repo tools are managed by mise, run under the repo environment so Go and converter plugins match CI:

eval "$(mise env)" && upgrade-provider pulumi/<provider> --repo-path . --no-submit --target-version <version>

Example: pulumi-rancher2 upgrading to upstream terraform-provider-rancher2 v14.1.0 needed main-module replacements like:

replace (
  github.com/rancher/rancher => github.com/rancher/rancher v0.0.0-20260226161459-b186acea1a52
  github.com/rancher/rancher/pkg/apis => github.com/rancher/rancher/pkg/apis v0.0.0-20260226161459-b186acea1a52
  github.com/rancher/rancher/pkg/client => github.com/rancher/rancher/pkg/client v0.0.0-20260226161459-b186acea1a52
)

In that case, copying upstream OpenTelemetry replacements caused conflicts with Pulumi/bridge dependencies; the narrower Rancher-focused replacements were sufficient.

Upstream provider edits vendored dependency but module graph is stale

When make tfgen or upgrade-provider fails compiling the upstream Terraform provider with missing fields or methods from one of its dependencies, check whether upstream edited a vendored copy of that dependency without publishing or requiring a matching module version.

Common symptoms:

unknown field Destination in struct literal of type "github.com/f5devcentral/go-bigip".Gtmmonitor
client.CreateGtmMonitor undefined
client.GetGtmMonitor undefined

Confirm before fixing:

  1. Identify the dependency package in the compiler errors.
  2. Inspect the upstream provider tag or commit and compare its vendor/<module>/... files against the module version selected by provider/go.mod.
  3. If the needed fields or methods exist only under upstream vendor/, treat this as an upstream vendored-dependency patch case.

Fix in the Pulumi provider repo:

  1. Add or update the upstream Terraform provider submodule at upstream, pinned to the target upstream tag or commit.
  2. Set .gitmodules for the submodule to include ignore = dirty so an applied patch queue does not leave top-level git status noisy.
  3. Use the upstream-patches skill and ./scripts/upstream.sh checkout / check_in workflow to add a new patch containing only minimal go.mod files in the affected vendored dependency directories. This is an allowed new-patch case because the provider needs durable vendored module metadata:
./scripts/upstream.sh checkout
cd upstream
# Add minimal go.mod files under vendor/<module>/...
git add vendor/<module>/go.mod
git commit -m "Add module metadata for vendored <module>"
cd ..
./scripts/upstream.sh check_in
  1. Add narrow replace directives in provider/go.mod that point only the stale dependency modules at ../upstream/vendor/<module>.
  2. Run go mod tidy from provider/, then rerun make tfgen or upgrade-provider.

Example:

replace github.com/f5devcentral/go-bigip => ../upstream/vendor/github.com/f5devcentral/go-bigip

replace github.com/f5devcentral/go-bigip/f5teem => ../upstream/vendor/github.com/f5devcentral/go-bigip/f5teem

Avoid:

  • Copying the dependency into provider/third_party or another provider-owned vendor directory.
  • Replacing the whole upstream Terraform provider module with ../upstream unless necessary; this can break bridge documentation and example discovery because the module cache layout changes.
  • Hand-editing patches/*.patch instead of using upstream-patches, unless intentionally doing raw patch surgery.

When adding this kind of patch, make the tracking issue explicit and de-duplicated:

  1. Search existing issues first:
gh issue list --label "area/patch" --search "vendored dependency <module> in:title,body" --json number,title,url
  1. If no matching issue exists, create one with gh issue create, labeled area/patch, explaining why the patch exists and when it can be removed:
gh issue create \
  --title "Track vendored dependency patch for <module>" \
  --label "area/patch" \
  --body "$(cat <<'EOF'
The provider upgrade needs a patch because upstream vendors dependency changes
that are not available from the published module graph.

Removal criteria:
- Upstream requires a published dependency module containing the vendored changes, or
- The provider no longer needs the dependency replace directives.

Verification:
- Delete the patch and related provider/go.mod replace directives.
- Run make tfgen.
EOF
)"
  1. Link the issue from the upgrade PR's "Fixes applied to unblock upgrade" section.

Removal criteria should usually be: upstream requires a published dependency module containing the vendored changes, or the provider no longer needs the dependency replace directives. Verify removal by deleting the patch and replace directives, then rerunning make tfgen.

New resources missing module mapping

When new upstream resources appear, token mapping can fail with errors like:

* "google_observability_trace_scope": could not find a module that prefixes 'observability_trace_scope' in '[...]'

Fix by updating provider/resources.go (or the repo-equivalent file):

  1. Determine the Terraform module name (usually the first segment after the provider prefix). In the example, use observability.
  2. Add a module mapping in moduleMapping:
var moduleMapping = map[string]string{
  "observability": "Observability",
}
  1. If related resources already map to a different module, map the new resource individually instead of adding a new module key. Example:
DataSources: map[string]*tfbridge.DataSourceInfo{
  "aws_vpn_gateway": {Tok: awsDataSource(ec2Mod, "getVpnGateway")},
}

After updating, rerun upgrade-provider with --no-submit.

.NET duplicate file from nested Get suffix collision

When make generate_sdks fails during .NET SDK generation with an error like:

panic: fatal: An assertion has failed: duplicate file: Chaos/Inputs/ProbeTemplateHttpProbeMethodGetArgs.cs

look for a newly-added nested schema type where a parent type and a child field/type collide with the .NET generator's helper suffixes. Common pattern:

  • Parent object type: <X>
  • Child field/type: <X>Get
  • .NET state helper for the parent: <X>GetArgs.cs
  • .NET input helper for the child: <X>GetArgs.cs

Confirm the shape was introduced by the upstream bump before renaming. Compare the generated schema on the default branch with the upgrade branch. Derive the branch and schema path instead of assuming provider-specific names:

default_branch=$(git remote show origin | sed -n 's/.*HEAD branch: //p')
schema_path=$(find provider/cmd -path '*/schema.json' -print -quit)
git show "origin/${default_branch}:${schema_path}" | rg "<NestedTypeName>"
rg "<NestedTypeName>" "$schema_path"

Replace <NestedTypeName> with the colliding nested type prefix from the duplicate filename. If rg is unavailable, use grep -n for these literal searches.

Fix by applying a normal bridge Name override in provider/resources.go to rename the smallest nested field that causes the collision. Do not use CSharpName; it only changes C# property labels and does not change generated nested type filenames. Avoid schema post-processors unless there is no ordinary bridge mapping available.

Example:

"harness_chaos_probe_template": {
  Tok: harnessResource("chaos", "ProbeTemplate"),
  Fields: map[string]*tfbridge.SchemaInfo{
    "http_probe": {
      Elem: &tfbridge.SchemaInfo{
        Fields: map[string]*tfbridge.SchemaInfo{
          "method": {
            Elem: &tfbridge.SchemaInfo{
              Fields: map[string]*tfbridge.SchemaInfo{
                "get": {Name: "getMethod"},
              },
            },
          },
        },
      },
    },
  },
},

After updating the bridge mapping, rerun upgrade-provider --no-submit from the repo root so schema and SDKs regenerate consistently.

ID attribute wrong type (tfgen unresolved ID mapping)

When make tfgen fails with an error like:

error: Resource linode_producer_image_share_group has a problem: "id" attribute is of type "Int", expected type "string". To map this resource consider overriding the SchemaInfo.Type field or specifying ResourceInfo.ComputeID
error: There were 1 unresolved ID mapping errors

The upstream resource has an id attribute, but it is not a string. Fix it in provider/resources.go (or equivalent) by applying the override:

prov.P.ResourcesMap().Range(func(key string, value shim.Resource) bool {
  if value.Schema().Get("id").Type() != shim.TypeString {
    r := prov.Resources[key]
    if r.Fields == nil {
      r.Fields = make(map[string]*tfbridge.SchemaInfo, 1)
    }
    r.Fields["id"] = &tfbridge.SchemaInfo{Type: "string"}
  }
  return true
})
  • If the id type is not coercible to string, set ResourceInfo.ComputeID instead.

ID attribute is input type (tfgen unresolved ID mapping)

When make tfgen fails with an error like:

error: Resource cloudflare_zero_trust_access_ai_controls_mcp_server has a problem: an "id" input attribute is not allowed. To map this resource specify SchemaInfo.Name and ResourceInfo.ComputeID

The upstream resource exposes id as Optional/Required. Remap the input field to <resource_name>_id and delegate the ID to that new property. Convert the field name to Pulumi camelCase (for example, cloudflare_zero_trust_access_ai_controls_mcp_server -> zeroTrustAccessAiControlsMcpServerId).

"cloudflare_zero_trust_access_ai_controls_mcp_server": {
  Fields: map[string]*info.Schema{
    "id": {
      Name: "zeroTrustAccessAiControlsMcpServerId",
    },
  },
  ComputeID: tfbridge.DelegateIDField(resource.PropertyKey("zeroTrustAccessAiControlsMcpServerId"),
    "cloudflare", "https://github.com/pulumi/pulumi-cloudflare"),
},

Source: SKILL.md on GitHub

No alerts16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill facilitates Pulumi provider upgrades and follows security best practices by requiring manual audit steps and using lease-protected git operations. No malicious patterns were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 8880e3c. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 3 months ago
  • Go
  • DevOps
  • pulumi
  • provider
  • upgrade
  • automation
  • ci-cd

README badge

README badge for pulumi/agent-skills/pulumi-upgrade-provider

Automates upgrades of Pulumi provider repositories to new upstream versions by running the `upgrade-provider` tool and fixing common failures like patch conflicts and missing module mappings. Targets provider maintainers working within the Pulumi ecosystem who need to synchronize their provider code with upstream Go module changes.

Generated from the current SKILL.md.

What does the upgrade-provider tool do?
It automates upgrades of Pulumi provider repositories to new upstream versions. You run the tool, check for errors in the output, fix known failures using the skill's error reference, and rerun until the upgrade succeeds.
How long does upgrade-provider take to run?
The tool can take up to 10 minutes to complete. You should wait for the full run before checking for errors in the output log.
When should I stop trying to fix errors and report failure?
Stop if the tool is not in PATH (exit code 127), you encounter the same error 3 times without success, the error is not covered in the skill's error reference and you cannot determine a safe fix, or the fix requires human judgment on breaking changes, deprecation strategies, or architectural decisions.
Can I manually commit, push, or create branches while using this skill?
No. The skill requires you to keep all git operations read-only; the upgrade-provider tool owns branch, commit, and PR state. Only `./scripts/upstream.sh` commands and read-only git queries are allowed.
What should I do if the upgrade succeeds?
Fetch the PR URL using read-only gh commands, and append a "Fixes applied to unblock upgrade" section to the PR body if you made any fixes, using the GitHub REST API to avoid overwriting existing content.

Generated from the current SKILL.md. These answers refresh after source changes.