Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions internal/provider/template_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,7 +1049,36 @@ func (d *versionsPlanModifier) PlanModifyList(ctx context.Context, req planmodif
return
}

resp.PlanValue, diag = types.ListValueFrom(ctx, req.PlanValue.ElementType(ctx), planVersions)
// Now patch the original plan elements with only the fields we changed
planElements := req.PlanValue.Elements()
newElements := make([]attr.Value, len(planElements))

for i, elem := range planElements {
obj, ok := elem.(types.Object)
if !ok {
resp.Diagnostics.AddError("Client Error", "Expected object element in versions list")
return
}

attrs := obj.Attributes()
attrTypes := obj.AttributeTypes(ctx)

// Overwrite only the fields the plan modifier manages
attrs["id"] = planVersions[i].ID
attrs["name"] = planVersions[i].Name
attrs["directory_hash"] = planVersions[i].DirectoryHash

// tf_vars, provisioner_tags, directory, active, message — all untouched

newObj, objDiag := types.ObjectValue(attrTypes, attrs)
if objDiag.HasError() {
resp.Diagnostics.Append(objDiag...)
return
}
newElements[i] = newObj
}

resp.PlanValue, diag = types.ListValue(req.PlanValue.ElementType(ctx), newElements)
if diag.HasError() {
resp.Diagnostics.Append(diag...)
}
Expand Down Expand Up @@ -1517,7 +1546,7 @@ func (planVersions Versions) reconcileVersionIDs(lv LastVersionsByHash, configVe
prevList := lv[planVersions[i].DirectoryHash.ValueString()]
if len(prevList) > 0 && planVersions[i].ID.IsUnknown() {
planVersions[i].ID = UUIDValue(prevList[0].ID)
if planVersions[i].Name.IsUnknown() {
if configVersions[i].Name.IsNull() {
planVersions[i].Name = types.StringValue(prevList[0].Name)
Comment on lines +1549 to 1550
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid reusing prior ID while leaving name unknown

When configVersions[i].Name is non-null but unknown (for example, a recreated upstream random_uuid), this branch reuses a prior version ID but intentionally leaves planVersions[i].Name unknown. In Update(), known IDs go through the metadata-update path, where !curVersion.Name.Equal(newState.Versions[idx].Name) is true and ValueString() on the unknown name becomes an empty string, causing an invalid rename request (or a wrong blank name update). This regression is introduced by changing the backfill condition here and can break applies for unknown-but-configured names.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

@jatcod3r jatcod3r May 13, 2026

Choose a reason for hiding this comment

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

When tf_vars change, the third loop resets the ID to unknown, so during Apply it goes through the "create new version" path where the name gets populated from the server response, so an empty name is expected. The unknown name never hits ValueString() in a rename request, or at least it shouldn't.

}
lv[planVersions[i].DirectoryHash.ValueString()] = prevList[1:]
Expand Down
60 changes: 59 additions & 1 deletion internal/provider/template_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1388,6 +1388,8 @@ func TestReconcileVersionIDs(t *testing.T) {
},
},
{
// Config name is null (auto-generated), plan name is unknown.
// Should backfill name from state since the user didn't set one.
Name: "UnknownUsesStateInOrder",
planVersions: []TemplateVersion{
{
Expand All @@ -1408,7 +1410,7 @@ func TestReconcileVersionIDs(t *testing.T) {
Name: types.StringValue("foo"),
},
{
Name: types.StringValue("bar"),
Name: types.StringNull(),
},
},
inputState: map[string][]PreviousTemplateVersion{
Expand Down Expand Up @@ -1440,6 +1442,62 @@ func TestReconcileVersionIDs(t *testing.T) {
},
},
},
{
// Config name is non-null (e.g. random_uuid.result), plan name is unknown
// because the upstream resource is being recreated.
// Should NOT backfill name — leave it unknown to resolve after apply.
Name: "UnknownNonNullConfigNameNotBackfilled",
planVersions: []TemplateVersion{
{
Name: types.StringValue("foo"),
DirectoryHash: types.StringValue("aaa"),
ID: NewUUIDUnknown(),
TerraformVariables: []Variable{},
},
{
Name: types.StringUnknown(),
DirectoryHash: types.StringValue("aaa"),
ID: NewUUIDUnknown(),
TerraformVariables: []Variable{},
},
},
configVersions: []TemplateVersion{
{
Name: types.StringValue("foo"),
},
{
Name: types.StringValue("bar"),
},
},
inputState: map[string][]PreviousTemplateVersion{
"aaa": {
{
ID: aUUID,
Name: "qux",
TFVars: map[string]string{},
},
{
ID: bUUID,
Name: "baz",
TFVars: map[string]string{},
},
},
},
expectedVersions: []TemplateVersion{
{
Name: types.StringValue("foo"),
DirectoryHash: types.StringValue("aaa"),
ID: UUIDValue(aUUID),
TerraformVariables: []Variable{},
},
{
Name: types.StringUnknown(),
DirectoryHash: types.StringValue("aaa"),
ID: UUIDValue(bUUID),
TerraformVariables: []Variable{},
},
},
},
{
Name: "NewVersionNewRandomName",
planVersions: []TemplateVersion{
Expand Down