Claude Code Plugins

Community-maintained marketplace

Feedback

|

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name rename-attribute
description Renames a Terraform attribute safely without changing its schema or producing an unnecessary diff, while enabling users to use both the previous and subsequent attribute names for a deprecation period.

Rename Terraform Attribute

Overview

A user may ask you to rename a Terraform attribute. A Terraform attribute has a name and a schema representing the types of data the attribute can contain. Renaming a Terraform attribute keeps the original attribute and creates a new attribute with the same schema, introducing a deprecation period so the user can migrate from the old attribute to the new attribute.

Workflow

Determine Attribute Schema

Use the "Determine Attribute Schema" section.

Create New Attribute

Use the "Create New Attribute" section.

Modify New and Old Attribute Schemas

Use the "Modify New and Old Attribute Schemas" section.

Modify CRUD Methods

Use the "Modify CRUD Methods" section.

Create Plan Modifier

Use the "Create Plan Modifier" section.

Write Tests

Use the "Write Tests" section.

Determine Attribute Schema

Find the Schema method on the resource and look for the attribute the user asked to be renamed. Take note of the schema for the attribute and whether the attribute is Required, Optional, or Computed. This is the schema that will need to be used for the new attribute.

Create New Attribute

Create a new attribute on the resource within the Schema method using the new name that the user told you. The schema for this attribute must be identical to the schema for the old attribute. The only difference should be the attribute name.

Modify New and Old Attribute Schemas

Struct Fields

There's a struct type that represents the resource that Terraform uses to serialize and deserialize Go types to Terraform types and vice versa. The type is usually something with "model" in the name. You should be able to find the field by looking for a tfsdk struct field tag with the value that matches the name of the attribute from Schema.

Once you find this struct, rename the field representing the old attribute and append Deprecated. Create a new field representing the new attribute with a CamelCase name based on the name the user gave you (e.g., NewAttribute).

Schema Definition

Update the schema for the both the new and old attributes with the following changes.

  • Remove Required.
  • Add Optional: true.
  • Add Computed: true.

Deprecation Message

Add DeprecationMessage to the old attribute with a message that tells the user to use the new attribute. Update the Description and/or MarkdownDescription for the old attribute to note that it's deprecated.

Validation

Add a validator.ExactlyOneOf validator to the old attribute with a path expression matching the new attribute. There should be no such validator on the new attribute.

Modify CRUD Methods

Create

At the top of the Create method there will be logic that loads the plan data into a variable. Find the variable name so we can use it for future operations.

var data ExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

Set values for the new and old attribute values in the data variable that was retrieved earlier. The values will likely be retrieved from an earlier API operation to create the resource. It must look something like this.

data.NewAttribute = // ... 
data.OldAttribute = // ...

Anywhere in Create that the old attribute was used, replace that usage with the following conditional logic. Change any to be the correct type for the attribute and use the correct Value method.

	var attribute any
	if !data.Hostname.IsNull() && !data.Hostname.IsUnknown() {
		attribute = data.NewAttribute.Value()
	} else {
		attribute = data.OldAttribute.Value()
	}

Read

At the top of the Read method there will be logic that loads the state data into a variable. Find the variable name so we can use it for future operations.

var data ExampleModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

Set values for the new and old attribute values in the data variable that was retrieved earlier. The values will likely be retrieved from an earlier API operation to read the resource. It must look something like this.

data.NewAttribute = // ... 
data.OldAttribute = // ...

Update

At the top of the Update method there will be logic that loads the plan data into a variable. Find the variable name so we can use it for future operations.

var data ExampleModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

Set values for the new and old attribute values in the data variable that was retrieved earlier. The values will likely be retrieved from an earlier API operation to update the resource. It must look something like this.

data.NewAttribute = // ... 
data.OldAttribute = // ...

Anywhere in Update that the old attribute was used, replace that usage with the following conditional logic. Change any to be the correct type for the attribute and use the correct Value method.

	var attribute any
	if !data.Hostname.IsNull() && !data.Hostname.IsUnknown() {
		attribute = data.NewAttribute.Value()
	} else {
		attribute = data.OldAttribute.Value()
	}

Create Plan Modifier

Create a plan modifier that both the old and new attribute will use within their PlanModifiers field using RequiresReplaceIf. The plan modifier must use the correct types for req and resp depending on the type of the attribute that's being renamed. Use the types package so that you get access to IsNull and IsUnknown.

Here's an example plan modifier for reference with TYPE meant to be replaced.

func ModifyPlanExample(ctx context.Context, req TYPE, resp TYPE) {
	// Check which attribute this modifier function is being called on to support
	// logic for both the old and the new attribute.
	switch attribute := req.Path.String(); attribute {
	case "NewAttribute":
		var deprecatedAttribute types.TYPE
		diags := req.Config.GetAttribute(ctx, path.Root("deprecated_attribute"), &deprecatedAttribute)
		resp.Diagnostics.Append(diags...)
		if resp.Diagnostics.HasError() {
			return
		}

		// The deprecated attribute has a value. We do not need to replace the resource
		// just because the new attribute doesn't have a value.
		if !deprecatedAttribute.IsNull() && !deprecatedAttribute.IsUnknown() {
			return
		}
	case "DeprecatedAttribute":
		var newAttribute types.TYPE
		diags := req.Config.GetAttribute(ctx, path.Root("new_attribute"), &newAttribute)
		resp.Diagnostics.Append(diags...)
		if resp.Diagnostics.HasError() {
			return
		}

		// The new attribute has a value. We do not need to replace the resource
		// just because the deprecated attribute doesn't have a value.
		if !newAttribute.IsNull() && !newAttribute.IsUnknown() {
			return
		}
	default:
		resp.Diagnostics.AddAttributeError(
			req.Path,
			fmt.Sprintf("Invalid plan modifier for attribute %s", attribute),
			"ModifyPlanExample can only be used for deprecated_attribute and new_attribute.",
		)
	}

	// If we've reached this point, it's because the actual value of either the
	// deprecated attribute or new attribute was modified, which must result in the
	// resource being replaced.
	resp.RequiresReplace = true
}

Write Tests

Write tests that test the following functionality. Utilize Go sub-tests where appropriate.

  • The provider can be updated to the version containing the rename with no configuration change and result in a no-op plan.
  • The attribute can be renamed from the old to the new name and vice versa and result in a no-op plan.
  • Changing the value of the attribute with no rename results in a resource replacement plan.
  • Changing the value of the attribute and renaming it results in a resource replacement plan.
  • The resource can be imported with eith the old or new attribute.
  • At least one of the old or new attribute must be provided. If none are provided or both are provided then assert on the error message.