Skip to content

Regression in reference field resolution with ptr.To() causing empty strings to be set instead of nil #103

@sergenyalcin

Description

@sergenyalcin

While updating some of the provider dependencies to use crossplane-runtime v1.19.0 and crossplane-tools v0.0.0-20250424174524-de0e5107ea45, we observed a behavioral change in how reference fields are resolved. This change introduced a subtle but impactful regression for optional fields, especially those of type *string.

Background

In previous versions of crossplane-tools, the generated reference resolvers would use the following pattern when assigning resolved values:

mg.Spec.ForProvider.Network = reference.ToPtrValue(rsp.ResolvedValue)

Where reference.ToPtrValue() would behave as:

func ToPtrValue(v string) *string {
	if v == "" {
		return nil
	}
	return &v
}

This ensured that empty strings were treated as nil. However, after the update, the generated code uses:

mg.Spec.ForProvider.Network = ptr.To(rsp.ResolvedValue)

And ptr.To() from k8s.io/utils/ptr does not perform the same check:

func To[T any](v T) *T {
	return &v
}

As a result, when no reference is specified and rsp.ResolvedValue == "", the resolved value becomes a pointer to an empty string instead of nil.

Impact

This change breaks existing assumptions and logic for several resources in the providers. In some cases, the provider starts applying updates unnecessarily, or backend APIs reject the empty string as invalid input, where a true nil was expected. Or we observe some server-side apply issues.

Suggested Fix

To restore the old behavior, we patched crossplane-tools locally and modified the generated resolver logic to explicitly check for an empty string:

if v := rsp.ResolvedValue; v != "" {
    mg.Spec.ForProvider.Network = ptr.To(v)
} else {
    mg.Spec.ForProvider.Network = nil
}

This aligns with the previous behavior of ToPtrValue() and ensures that optional string fields remain nil unless a valid value is resolved.

Reference PR: crossplane-contrib/provider-upjet-gcp#779

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