Problem
After a pulsar_function resource is successfully created or updated, the provider normalises the local file path supplied in py, jar, or go into a function://tenant/namespace/name cloud reference URL when it reads the resource back. Terraform then compares this cloud reference (in state) against the original local file path (in config) on every subsequent plan and always reports a change — even though applying it simply re-uploads the same bytes and the function is otherwise identical.
Minimal reproduction
resource "pulsar_function" "example" {
tenant = "my-tenant"
namespace = "my-namespace"
name = "my-function"
py = "./functions/my_function.py"
classname = "my_function.MyFunction"
inputs = ["persistent://my-tenant/my-namespace/input"]
output = "persistent://my-tenant/my-namespace/output"
}
After terraform apply succeeds, every subsequent terraform plan shows:
~ py = "function://my-tenant/my-namespace/my-function" -> "./functions/my_function.py"
Root cause
The provider's Read function retrieves the function definition from the Pulsar Admin API, which returns the deployed package as a function:// cloud reference. This value is written back to state. On the next plan, Terraform compares:
- state:
function://my-tenant/my-namespace/my-function (what the API returned)
- config:
./functions/my_function.py (what the user wrote)
Because there is no DiffSuppressFunc on py, jar, or go, Terraform treats this as a real diff and plans an update indefinitely.
Impact
- Every
terraform plan shows spurious changes for all deployed functions.
- Applying is idempotent (re-uploads the same package bytes) but wastes time and creates noise in CI pipelines and audit logs.
- Teams lose confidence in plan output because functions always appear dirty.
Proposed fix
Add a DiffSuppressFunc to the py, jar, and go schema fields that suppresses the diff when the deployed form (old) is already a function:// cloud reference and the desired form (new) is a local file path:
// suppressFunctionPackagePathDiff suppresses the diff when the provider has
// normalised a local file path to a function:// cloud reference after upload.
// Applying again would re-upload the same bytes, so the diff is noise.
// To force a redeployment after a code change, use terraform taint.
func suppressFunctionPackagePathDiff(_, old, new string, _ *schema.ResourceData) bool {
return strings.HasPrefix(old, "function://") && !strings.HasPrefix(new, "function://")
}
Applied to the schema fields:
resourceFunctionJarKey: {
Type: schema.TypeString,
Optional: true,
Description: resourceFunctionDescriptions[resourceFunctionJarKey],
DiffSuppressFunc: suppressFunctionPackagePathDiff,
},
resourceFunctionPyKey: {
Type: schema.TypeString,
Optional: true,
Description: resourceFunctionDescriptions[resourceFunctionPyKey],
DiffSuppressFunc: suppressFunctionPackagePathDiff,
},
resourceFunctionGoKey: {
Type: schema.TypeString,
Optional: true,
Description: resourceFunctionDescriptions[resourceFunctionGoKey],
DiffSuppressFunc: suppressFunctionPackagePathDiff,
},
Behaviour with the fix
| Scenario |
Suppressed? |
Correct? |
| First deployment (no prior state) |
No — old is empty |
✅ |
| Re-plan after deploy, same local path |
Yes — old is function://, new is local path |
✅ |
| Local path changed to a different file |
Yes — suppressed (see note below) |
⚠️ |
Config explicitly sets a function:// ref |
No — new starts with function:// |
✅ |
Note on path changes: the suppression also silences diffs when the local path itself changes in config (e.g. a.py → b.py), since the condition only checks whether old is a cloud ref and new is not. Users who update function code should use terraform taint to force a redeployment, or track file content via a hash in user_config. This is the same trade-off as other Terraform resources that manage uploaded artifacts (e.g. aws_lambda_function's filename field with source_code_hash).
Environment
- Provider version: v0.9.0
- Terraform version: 1.x
- Platform: darwin/arm64 (also reproducible on linux/amd64)
Problem
After a
pulsar_functionresource is successfully created or updated, the provider normalises the local file path supplied inpy,jar, orgointo afunction://tenant/namespace/namecloud reference URL when it reads the resource back. Terraform then compares this cloud reference (in state) against the original local file path (in config) on every subsequent plan and always reports a change — even though applying it simply re-uploads the same bytes and the function is otherwise identical.Minimal reproduction
After
terraform applysucceeds, every subsequentterraform planshows:Root cause
The provider's
Readfunction retrieves the function definition from the Pulsar Admin API, which returns the deployed package as afunction://cloud reference. This value is written back to state. On the next plan, Terraform compares:function://my-tenant/my-namespace/my-function(what the API returned)./functions/my_function.py(what the user wrote)Because there is no
DiffSuppressFunconpy,jar, orgo, Terraform treats this as a real diff and plans an update indefinitely.Impact
terraform planshows spurious changes for all deployed functions.Proposed fix
Add a
DiffSuppressFuncto thepy,jar, andgoschema fields that suppresses the diff when the deployed form (old) is already afunction://cloud reference and the desired form (new) is a local file path:Applied to the schema fields:
Behaviour with the fix
oldis emptyoldisfunction://,newis local pathfunction://refnewstarts withfunction://Note on path changes: the suppression also silences diffs when the local path itself changes in config (e.g.
a.py→b.py), since the condition only checks whetheroldis a cloud ref andnewis not. Users who update function code should useterraform taintto force a redeployment, or track file content via a hash inuser_config. This is the same trade-off as other Terraform resources that manage uploaded artifacts (e.g.aws_lambda_function'sfilenamefield withsource_code_hash).Environment