Terraform
Resource Identity
A resource identity is a data object, determined by the provider, that is stored alongside the resource state to uniquely identify a remote object. Resource identity is supported in Terraform 1.12 and later.
A resource identity should have the following properties:
- The resource identity must correspond to at most one remote object per provider, across all instances of that provider.
- Given a resource identity (during import), the provider must be able to determine whether the corresponding remote object exists, and if so, return the resource state.
- The identity data for a remote object must not change during its lifecycle from creation to deletion, or until the provider upgrades the resource identity schema.
Schema
To define the identity object for a managed resource, a resource identity schema is provided that consists of a map of attribute names and associated behaviors.
The resource identity schema is similar to the resource state schema, but with the following differences:
- The identity schema only supports primitive types and list types, which are represented by the following attributes:
- Unlike the resource state schema, the resource identity schema does not support behaviors such as RequiredandComputed. All identity data is set by the provider, so the entire object is treated asComputed. Two behaviors are allowed for each identity schema to assist with importing a resource by identity,RequiredForImportandOptionalForImport.- RequiredForImportonly: A practitioner must configure the attribute to a known value (not- null) during import, otherwise Terraform automatically raises an error diagnostic for the missing value.
- OptionalForImportonly: A practitioner must configure the value to a known value or- nullduring import.
 
It is expected that exactly one of RequiredForImport or OptionalForImport is set to true. Regardless of which option is chosen, the provider can decide exactly what data is stored in the identity during import, similar to Computed attributes in resource state.
Defining Identity
The identity schema can be set in a new IdentitySchema method, which is defined in the resource.ResourceWithIdentity interface:
var _ resource.ResourceWithIdentity = ThingResource{} // new interface
func (r ThingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
  resp.TypeName = "examplecloud_thing"
}
func (r ThingResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
  resp.Schema = schema.Schema{
    // .. resource schema for examplecloud_thing
  }
}
// Struct model for identity data handling
type ThingResourceIdentityModel struct {
  ID        types.String `tfsdk:"id"`
  Region    types.String `tfsdk:"region"`
}
func (r ThingResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) {
  resp.IdentitySchema = identityschema.Schema{
    Attributes: map[string]identityschema.Attribute{
      "id": identityschema.StringAttribute{
        RequiredForImport: true, // must be set during import by the practitioner
      },
      "region": identityschema.StringAttribute{
        OptionalForImport: true, // can be defaulted by the provider configuration
      },
    },
  }
}
Handling Identity Data
Identity data, similar to resource state data, can be set or retrieved during the resource Create,
Read, Update, Delete
and ImportState methods. Unlike resource state data, identity data is expected to be immutable after it is set during
Create, so typically the only locations a provider should need to write identity data is during Create and
Read.
Read should return identity data so that the managed resource can support importing,
especially if not all of the identity attributes are provided by the practitioner during import (like provider configuration values and remote API data).
Writing Identity
Typically, identity data should be set during Create and Read.
The same data model for writing data to state is used for identity, for example:
// .. rest of resource implementation
type ThingResourceModel struct {
  // state data model
}
// identity data model
type ThingResourceIdentityModel struct {
  ID        types.String `tfsdk:"id"`
  Region    types.String `tfsdk:"region"`
}
func (r ThingResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
  // Read plan data
  // Call remote API to create resource (i.e. apiResp)
  // Set data returned by API in state
  data.ID = types.StringValue(apiResp.ID)
  resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
  // Set data returned by API in identity
  identity := ThingResourceIdentityModel{
    ID:     types.StringValue(apiResp.ID),
    Region: types.StringValue(apiResp.Region),
  }
  resp.Diagnostics.Append(resp.Identity.Set(ctx, identity)...)
}
Reading Identity
The same data model for reading data from state is used for identity, for example:
// .. rest of resource implementation
type ThingResourceModel struct {
  // state data model
}
// identity data model
type ThingResourceIdentityModel struct {
  ID        types.String `tfsdk:"id"`
  Region    types.String `tfsdk:"region"`
}
func (r ThingResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
  // Read state data
  var stateData ThingResourceModel
  resp.Diagnostics.Append(req.State.Get(ctx, &stateData)...)
  if resp.Diagnostics.HasError() {
    return
  }
  // Read identity data
  var identityData ThingResourceIdentityModel
  resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
  if resp.Diagnostics.HasError() {
    return
  }
  // Call remote API to read resource
  // Set data returned by API in state
  // Set data returned by API in identity
}
Importing by Identity
Managed resources that define an identity can be imported by either the id or the resource identity.
The user must set either id or identity and not both. Supplying both or none will result in a validation error. For example, the identity presented in the
"Define Identity" section, can be imported via the following methods:
- terraform importCLI command with ID string
terraform import examplecloud_thing.test id-123
- importblock with- idargument
import {
  to = examplecloud_thing.test
  id = "id-123"
}
- importblock with- identityargument
import {
  to = examplecloud_thing.test
  identity = {
    id = "id-123" # required for import
    region = "us-east-1" # optional for import
  }
}
If identity data is present in the request, the provider is expected to ignore anything in the resource.ImportStateRequest.ID field (which Core will set to ""). To maintain compatibility with the terraform import CLI command and the import block with id field, providers must continue to support importing via import ID if the identity data is not present.
For resources that only need to support Terraform v1.12+, providers may choose not to support an import ID at all. In this case, if the user supplies an import ID (via the terraform import CLI command or in an import block), Terraform will send an import request to the provider including a non-empty resource.ImportStateRequest.ID field, and the provider can choose to return an error with the resource.ImportStateResponse.Diagnostics field saying that it is not supported.
An example ImportState implementation that accounts for both importing by id and importing by identity:
func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
  // If importing by ID, we just set the ID field to state, allowing the read to fill in the rest of the data.
  if req.ID != "" {
    resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...)
    return
  }
  // Otherwise, we're importing by identity. We can either let identity passthrough
  // to Read, or we can read the identity and use it to set data to state to be used in Read.
  var identityData ThingResourceIdentityModel
  resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...)
  if resp.Diagnostics.HasError() {
    return
  }
  resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), identityData.ID)...)
}
If the identity is a single attribute that is passed through to a single attribute in state, the resource.ImportStatePassthroughWithIdentity
helper method can be used, which will set the same state attribute with either identity data or the ID string field:
func (r ThingResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
  resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("id"), req, resp)
}
Mutable Identities
By default, if identity data unexpectedly changes during the resource's lifecycle, an error will be raised by the framework:
Error: Unexpected Identity Change
During the <update/read/planning> operation, the Terraform Provider unexpectedly returned a
different identity then the previously stored one. 
If the remote object has an identity that can be changed without being destroyed/re-created, this validation
can be disabled by setting the resource.ResourceBehavior
MutableIdentity field to true, which is set in the Metadata method on a resource:
func (r ThingResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
  resp.TypeName = "examplecloud_thing"
  resp.ResourceBehavior = resource.ResourceBehavior{
    MutableIdentity: true,
  }
}