CRUD functions
Note: The Plugin Framework is in beta.
In Terraform, a resource represents a single instance of a given resource type. They modify a specific resource in the API and in Terraform's state through a set of Create, Read, Update, and Delete (CRUD) functions. A resource's CRUD functions implement the logic required to manage your resources with Terraform. Refer to Resources - Define Resources in the Framework documentation for details.
This page explains how to migrate a resource's CRUD functions from SDKv2 to the plugin Framework.
SDKv2
In SDKv2, a resource's CRUD functions are defined by populating the relevant fields (e.g., CreateContext
,
ReadContext
) on the schema.Resource
struct.
The following code shows a basic implementation of CRUD functions with SDKv2.
func resourceExample() *schema.Resource {
return &schema.Resource{
CreateContext: create,
ReadContext: read,
UpdateContext: update,
DeleteContext: delete,
/* ... */
Framework
In the Framework, you implement CRUD functions for your resource by defining a type that implements the
resource.Resource
interface. To define functions related to state upgrade, import, and plan modification,
implement their respective interfaces on your resource: ResourceWithUpgradeState
, ResourceWithImportState
, and
ResourceWithModifyPlan
.
The following code shows how you define a resource.Resource
which implements CRUD functions with the Framework.
type resourceExample struct {
p provider
}
func (r *resourceExample) Create(ctx context.Context, req resource.CreateRequest, resp
*resource.CreateResponse) {
/* ... */
}
func (r *resourceExample) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
/* ... */
}
func (r *resourceExample) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
/* ... */
}
func (r *resourceExample) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
/* ... */
}
Migration Notes
Remember the following differences between SDKv2 and the Framework when completing the migration.
- In SDKv2 it is not necessary to define functions for parts of the CRUD lifecycle that are not used by a given
resource. For instance, if the resource does not support in-place modification, you do not need to define an
Update
function. In the Framework, you must implement each of the CRUD lifecycle functions on all resources to satisfy theResource
interface, even if the function does nothing. - In SDKv2, the
Update
function (even if empty or missing) would automatically copy the request plan to the response state. This could be problematic when theUpdate
function also returned errors as additional steps were required to disable the automatic SDKv2 behavior. In the Framework, theUpdate
method must be written to explicitly copy data fromreq.Plan
toresp.State
to actually update the resource's state and preventProvider produced inconsistent result after apply
errors from Terraform. - In SDKv2, calling
d.SetId("")
would signal resource removal. In the Framework, this is replaced withresp.State.RemoveResource()
. Resource removal should only occur during theRead
method to preventProvider produced inconsistent result after apply
errors from Terraform during other operations. TheDelete
method will automatically callresp.State.RemoveResource()
if there are no errors. - In SDKv2, you get and set attribute values in Terraform's state by calling
Get()
andSet()
onschema.ResourceData
. In the Framework, you get attribute values from the configuration and plan by accessingConfig
andPlan
onresource.CreateRequest
. You set attribute values in Terraform's state by mutatingState
onresource.CreateResponse
.
Example
The following examples show how to migrate portions of the random provider.
For a complete example, clone the
terraform-provider-random
repository and compare the resource_password.go
file in
v3.3.2
with v3.4.1.
SDKv2
The following example from the resource_password.go
file shows implementations of CRUD functions on the
random_password
resource with SDKv2. The UpdateContext
function is not implemented because the provider does not
support updating this resource.
func resourcePassword() *schema.Resource {
return &schema.Resource{
CreateContext: createPassword,
ReadContext: readNil,
DeleteContext: RemoveResourceFromState,
/* ... */
The following example shows the implementation of the createPassword()
function with SDKv2. The implementations of
the readNil()
and RemoveResourceFromState()
functions are not shown for brevity.
func createPassword(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
diags := createStringFunc(true)(ctx, d, meta)
if diags.HasError() {
return diags
}
hash, err := generateHash(d.Get("result").(string))
if err != nil {
diags = append(diags, diag.Errorf("err: %s", err)...)
return diags
}
if err := d.Set("bcrypt_hash", hash); err != nil {
diags = append(diags, diag.Errorf("err: %s", err)...)
return diags
}
return nil
}
Framework
The following shows the same section of provider code after the migration.
This code implements the Create
function for the random_password
resource with the Framework.
func (r *passwordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan passwordModelV2
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
params := random.StringParams{
Length: plan.Length.ValueInt64(),
Upper: plan.Upper.ValueBool(),
MinUpper: plan.MinUpper.ValueInt64(),
Lower: plan.Lower.ValueBool(),
MinLower: plan.MinLower.ValueInt64(),
Numeric: plan.Numeric.ValueBool(),
MinNumeric: plan.MinNumeric.ValueInt64(),
Special: plan.Special.ValueBool(),
MinSpecial: plan.MinSpecial.ValueInt64(),
OverrideSpecial: plan.OverrideSpecial.ValueString(),
}
result, err := random.CreateString(params)
if err != nil {
resp.Diagnostics.Append(diagnostics.RandomReadError(err.Error())...)
return
}
hash, err := generateHash(string(result))
if err != nil {
resp.Diagnostics.Append(diagnostics.HashGenerationError(err.Error())...)
}
plan.BcryptHash = types.StringValue(hash)
plan.ID = types.StringValue("none")
plan.Result = types.StringValue(string(result))
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
}