Terraform is trusted for managing many facets of infrastructure across many organizations. Part of that trust is due to consistent versioning guidelines and setting expectations for various levels of upgrades. Ensuring backwards compatibility for all patch and minor releases, potentially in concert with any upcoming major changes, is recommended and supported by the Terraform development framework. This allows operators to iteratively update their Terraform configurations rather than require massive refactoring.
This guide is designed to walk through various scenarios where existing Terraform functionality requires future removal, while maintaining backwards compatibility. Further information about the versioning terminology (e.g. MAJOR.MINOR.PATCH) in this guide can be found in the versioning guidelines documentation.
NOTE: Removals should only ever occur in MAJOR version upgrades.
The recommended process for removing an attribute from a data source or resource in a provider is as follows:
Add Deprecated in the attribute schema definition. After an operator upgrades to this version, they will be shown a warning with the message provided when using the attribute, but the Terraform run will still complete.
Ensure the changelog has an entry noting the deprecation.
Release a MINOR version with the deprecation.
In the next MAJOR version, remove all code associated with the attribute including the schema definition.
Ensure the changelog has an entry noting the removal.
When renaming an attribute from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the Terraform state while operators migrate. To accomplish this, there will be some duplicated logic to support both attributes until the next MAJOR release. Once both attributes are appropriately handled, the process for deprecating and removing the old attribute is the same as noted in the Provider Attribute Removal section.
The procedure for renaming an attribute depends on what type of attribute it is:
Required attributes are also referred to as required "arguments" throughout the Terraform documentation.
In general, the procedure here does two things:
Prevents the operator from needing to define two attributes with the same value.
Allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in terraform plan output format:
Replace Required: true with Optional: true in the existing attribute schema definition.
Replace Required with Optional in the existing attribute documentation.
Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name.
Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name.
Add Deprecated to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message.
Add **Deprecated** to the documentation of the existing (now the "old") attribute, noting to use the new attribute.
Add a note to the documentation that either the existing (now the "old") attribute or new attribute must be configured.
Add ConflictsWith to the schema definitions of both the old and new attributes so they will present an error to the operator if both are configured at the same time.
Add conditional logic in the Create, Read, and Update functions of the data source or resource to handle both attributes. Generally, this involves using ResourceData.GetOk() (commonly d.GetOk() in HashiCorp maintained providers).
Add conditional logic in the Create and Update function that returns an error if both the old and new attributes are not defined.
Follow the rest of the procedures in the Provider Attribute Removal section. When the old attribute is removed, update the schema definition and documentation of the new attribute back to Required.
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Required:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...
existingAttribute := d.Get("existing_attribute").(string)// add attribute to provider create API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...
existingAttribute := d.Get("existing_attribute").(string)// add attribute to provider update API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Required:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ... existingAttribute := d.Get("existing_attribute").(string)// add attribute to provider create API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ... existingAttribute := d.Get("existing_attribute").(string)// add attribute to provider update API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
In order to support renaming existing_attribute to new_attribute, this sample can be written as the following to support both attributes simultaneously until the existing_attribute is removed:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Optional:true,
ConflictsWith:[]string{"new_attribute"},
Deprecated:"use new_attribute instead",},"new_attribute":{
Type: schema.TypeString,
Optional:true,
ConflictsWith:[]string{"existing_attribute"},},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...
existingAttribute, existingAttributeOk := d.GetOk("existing_attribute")
newAttribute, newAttributeOk := d.GetOk("new_attribute")if!existingAttributeOk &&!newAttributeOk {return errors.New("one of existing_attribute or new_attribute must be configured")}if existingAttributeOk {// add existingAttribute to provider create API call}else{// add newAttribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...if_, ok := d.GetOk("existing_attribute"); ok {
d.Set("existing_attribute",/* ... */)}else{
d.Set("new_attribute",/* ... */)}// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...
existingAttribute, existingAttributeOk := d.GetOk("existing_attribute")
newAttribute, newAttributeOk := d.GetOk("new_attribute")if!existingAttributeOk &&!newAttributeOk {return errors.New("one of existing_attribute or new_attribute must be configured")}if existingAttributeOk {// add existingAttribute to provider update API call}else{// add newAttribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Optional:true, ConflictsWith:[]string{"new_attribute"}, Deprecated:"use new_attribute instead",},"new_attribute":{ Type: schema.TypeString, Optional:true, ConflictsWith:[]string{"existing_attribute"},},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ... existingAttribute, existingAttributeOk := d.GetOk("existing_attribute") newAttribute, newAttributeOk := d.GetOk("new_attribute")if!existingAttributeOk &&!newAttributeOk {return errors.New("one of existing_attribute or new_attribute must be configured")}if existingAttributeOk {// add existingAttribute to provider create API call}else{// add newAttribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...if_, ok := d.GetOk("existing_attribute"); ok { d.Set("existing_attribute",/* ... */)}else{ d.Set("new_attribute",/* ... */)}// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ... existingAttribute, existingAttributeOk := d.GetOk("existing_attribute") newAttribute, newAttributeOk := d.GetOk("new_attribute")if!existingAttributeOk &&!newAttributeOk {return errors.New("one of existing_attribute or new_attribute must be configured")}if existingAttributeOk {// add existingAttribute to provider update API call}else{// add newAttribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
When the existing_attribute is ready for removal, then this can be written as:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{
Type: schema.TypeString,
Required:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...
newAttribute := d.Get("new_attribute").(string)// add attribute to provider create API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...
newAttribute := d.Get("new_attribute").(string)// add attribute to provider update API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{ Type: schema.TypeString, Required:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ... newAttribute := d.Get("new_attribute").(string)// add attribute to provider create API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ... newAttribute := d.Get("new_attribute").(string)// add attribute to provider update API call// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
Optional attributes are also referred to as optional "arguments" throughout the Terraform documentation.
In general, the procedure here allows the operator to migrate the configuration to the new attribute at the same time requiring that any other references only work with the new attribute. This is to prevent a situation with Terraform showing a difference when the existing attribute is configured, but the new attribute is saved into the Terraform state. For example, in terraform plan output format:
Duplicate the schema definition of the existing attribute, renaming one of them with the new attribute name.
Duplicate the documentation of the existing attribute, renaming one of them with the new attribute name.
Add Deprecated to the schema definition of the existing (now the "old") attribute, noting to use the new attribute in the message.
Add **Deprecated** to the documentation of the existing (now the "old") attribute, noting to use the new attribute.
Add ConflictsWith to the schema definitions of both the old and new attributes so they will present an error to the operator if both are configured at the same time.
Add conditional logic in the Create, Read, and Update functions of the data source or resource to handle both attributes. Generally, this involves using ResourceData.GetOk() (commonly d.GetOk() in HashiCorp maintained providers).
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Optional:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Optional:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
In order to support renaming existing_attribute to new_attribute, this sample can be written as the following to support both attributes simultaneously until the existing_attribute is removed:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Optional:true,
ConflictsWith:[]string{"new_attribute"},
Deprecated:"use new_attribute instead",},"new_attribute":{
Type: schema.TypeString,
Optional:true,
ConflictsWith:[]string{"existing_attribute"},},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider create API call}elseif v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {
d.Set("existing_attribute",/* ... */)}else{
d.Set("new_attribute",/* ... */)}// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider update API call}elseif v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Optional:true, ConflictsWith:[]string{"new_attribute"}, Deprecated:"use new_attribute instead",},"new_attribute":{ Type: schema.TypeString, Optional:true, ConflictsWith:[]string{"existing_attribute"},},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider create API call}elseif v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok { d.Set("existing_attribute",/* ... */)}else{ d.Set("new_attribute",/* ... */)}// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("existing_attribute"); ok {// add attribute to provider update API call}elseif v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
When the existing_attribute is ready for removal, then this can be written as:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create: resourceExampleWidgetCreate,
Read: resourceExampleWidgetRead,
Update: resourceExampleWidgetUpdate,
Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{
Type: schema.TypeString,
Optional:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create: resourceExampleWidgetCreate, Read: resourceExampleWidgetRead, Update: resourceExampleWidgetUpdate, Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{ Type: schema.TypeString, Optional:true,},},}}funcresourceExampleWidgetCreate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider create API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}funcresourceExampleWidgetUpdate(d *schema.ResourceData, meta any)error{// ... other logic ...if v, ok := d.GetOk("new_attribute"); ok {// add attribute to provider update API call}// ... other logic ...returnresourceExampleWidgetRead(d, meta)}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Read: resourceExampleWidgetRead,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Read: resourceExampleWidgetRead, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("existing_attribute",/* ... */)// ... other logic ...returnnil}
In order to support renaming existing_attribute to new_attribute, this sample can be written as the following to support both attributes simultaneously until the existing_attribute is removed:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Read: resourceExampleWidgetRead,
Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{
Type: schema.TypeString,
Computed:true,
Deprecated:"use new_attribute instead",},"new_attribute":{
Type: schema.TypeString,
Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("existing_attribute",/* ... */)
d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Read: resourceExampleWidgetRead, Schema:map[string]*schema.Schema{// ... other attributes ..."existing_attribute":{ Type: schema.TypeString, Computed:true, Deprecated:"use new_attribute instead",},"new_attribute":{ Type: schema.TypeString, Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("existing_attribute",/* ... */) d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}
When the existing_attribute is ready for removal, then this can be written as:
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Read: resourceExampleWidgetRead,
Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{
Type: schema.TypeString,
Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ...
d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}
funcresourceExampleWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Read: resourceExampleWidgetRead, Schema:map[string]*schema.Schema{// ... other attributes ..."new_attribute":{ Type: schema.TypeString, Computed:true,},},}}funcresourceExampleWidgetRead(d *schema.ResourceData, meta any)error{// ... other logic ... d.Set("new_attribute",/* ... */)// ... other logic ...returnnil}
The recommended process for removing a data source or resource from a provider is as follows:
Add DeprecationMessage in the data source or resource schema definition. After an operator upgrades to this version, they will be shown a warning with the message provided when using the deprecated data source or resource, but the Terraform run will still complete.
Ensure the changelog has an entry noting the deprecation.
Release a MINOR version with the deprecation.
In the next MAJOR version, remove all code associated with the deprecated data source or resource except for the schema and replace the Create and Read functions to always return an error. Remove the documentation sidebar link and update the resource or data source documentation page to include information about the removal and any potential migration information. After an operator upgrades to this version, they will be shown an error about the missing data source or resource.
Ensure the changelog has an entry noting the removal.
Release the MAJOR version.
In the next MAJOR version, remove all code associated with the removed data source or resource. Remove the resource or data source documentation page.
When renaming a resource from one name to another, it is important to keep backwards compatibility with both existing Terraform configurations and the Terraform state while operators migrate. To accomplish this, there will be some duplicated logic to support both resources until the next MAJOR release. Once both resources are appropriately handled, the process for deprecating and removing the old resource is the same as noted in the Provider Data Source or Resource Removal section.
The recommended process is as follows:
Duplicate the code of the existing resource, renaming (and potentially modifying) functions as necessary.
Duplicate the documentation of the existing resource, renaming (and potentially modifying) as necessary.
Add DeprecatedMessage to the schema definition of the existing (now the "old") resource, noting to use the new resource in the message.
Add !> **WARNING:** This resource is deprecated and will be removed in the next major version to the documentation of the existing (now the "old") resource, noting to use the new resource.
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ...
ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ... ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
In order to support renaming example_existing_widget to example_new_widget, this sample can be written as the following to support both attributes simultaneously until the existing_attribute is removed:
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ...
ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),"example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
DeprecationMessage:"use example_new_widget resource instead"}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ... ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),"example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... DeprecationMessage:"use example_new_widget resource instead"}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
To soft remove example_existing_widget with a friendly error message:
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ...
ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),"example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...
Create:func(d *schema.ResourceData, meta any)error{return errors.New("use example_new_widget resource instead")},
Read:func(d *schema.ResourceData, meta any)error{return errors.New("use example_new_widget resource instead")},}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ... ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_existing_widget":resourceExampleExistingWidget(),"example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleExistingWidget()*schema.Resource {return&schema.Resource{// ... other configuration ... Create:func(d *schema.ResourceData, meta any)error{return errors.New("use example_new_widget resource instead")}, Read:func(d *schema.ResourceData, meta any)error{return errors.New("use example_new_widget resource instead")},}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
To remove example_existing_widget:
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ...
ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}
funcProvider() terraform.ResourceProvider {return&schema.Provider{// ... other configuration ... ResourcesMap:map[string]*schema.Resource{// ... other resources ..."example_new_widget":resourceExampleNewWidget(),},}}funcresourceExampleNewWidget()*schema.Resource {return&schema.Resource{// ... other configuration ...}}