Skip to content

pulsar_function: py/jar/go fields cause perpetual plan drift after first deployment #203

Description

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.pyb.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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions