Skip to content

Commit 512cdf7

Browse files
committed
docs/design: Upgrade Resource State background and goals
Reference: #42
1 parent fa1b020 commit 512cdf7

File tree

1 file changed

+234
-0
lines changed

1 file changed

+234
-0
lines changed

docs/design/upgrade-resource-state.md

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Upgrade Resource State
2+
3+
A resource schema captures the structure and types of the resource state. Any state data that is incorrect from the resource schema will generate errors or not be persisted. Over time, it may be necessary for resources to receive updates to the resource schema. Terraform supports versioning of these resource schemas, which is saved into the Terraform state. When the provider advertises a newer schema version, Terraform will call back to the provider to attempt to upgrade from the saved state version to the one advertised. This operation is performed prior to planning, but with a configured provider.
4+
5+
## Background
6+
7+
The resource state handling between the Terraform CLI and the provider is fairly transparent to practitioners as it is implemented without any particular user interface. Resource state upgrade operations happen as part of the general planning workflow. Practitioners will only have issues if any potential upgrades are incorrectly implemented, such as mismatched types, which will generate errors and likely require further provider action (e.g. a new release with fixed upgrade) or worst case of manual state manipulation.
8+
9+
The next sections will outline some of the underlying details relevant to implementation proposals in this framework.
10+
11+
### Terraform Plugin Protocol
12+
13+
The specification between Terraform CLI and plugins, such as Terraform Providers, is currently implemented via [Protocol Buffers](https://developers.google.com/protocol-buffers). Below highlights some of the service `rpc` (called by the Terraform CLI) and `message` types that are intergral for upgrade resource state support.
14+
15+
#### `UpgradeResourceState` RPC
16+
17+
```protobuf
18+
service Provider {
19+
// ...
20+
rpc UpgradeResourceState(UpgradeResourceState.Request) returns (UpgradeResourceState.Response);
21+
}
22+
23+
message UpgradeResourceState {
24+
message Request {
25+
string type_name = 1;
26+
27+
// version is the schema_version number recorded in the state file
28+
int64 version = 2;
29+
30+
// raw_state is the raw states as stored for the resource. Core does
31+
// not have access to the schema of prior_version, so it's the
32+
// provider's responsibility to interpret this value using the
33+
// appropriate older schema. The raw_state will be the json encoded
34+
// state, or a legacy flat-mapped format.
35+
RawState raw_state = 3;
36+
}
37+
message Response {
38+
// new_state is a msgpack-encoded data structure that, when interpreted with
39+
// the _current_ schema for this resource type, is functionally equivalent to
40+
// that which was given in prior_state_raw.
41+
DynamicValue upgraded_state = 1;
42+
43+
// diagnostics describes any errors encountered during migration that could not
44+
// be safely resolved, and warnings about any possibly-risky assumptions made
45+
// in the upgrade process.
46+
repeated Diagnostic diagnostics = 2;
47+
}
48+
}
49+
```
50+
51+
### terraform-plugin-go
52+
53+
The [`terraform-plugin-go` library](https://pkg.go.dev/hashicorp/terraform-plugin-go) is a low-level implementation of the [Terraform Plugin Protocol](#terraform-plugin-protocol) in Go and underpins this framework. This includes packages such as `tfprotov6` and `tftypes`. These are mentioned for completeness as some of these types are not yet abstracted in this framework and may be shown in implementation proposals.
54+
55+
### terraform-plugin-framework
56+
57+
Most of the Go types and functionality from `terraform-plugin-go` will be abstracted by this framework before reaching provider developers. The details represented here are not finalized as this framework is still being designed, however these current details are presented here for additional context in the later proposals.
58+
59+
Managed resources are currently implemented in the `ResourceType` and `Resource` Go interface types. Provider implementations are responsible for implementing these as concrete Go types.
60+
61+
```go
62+
// A ResourceType is a type of resource. For each type of resource this provider
63+
// supports, it should define a type implementing ResourceType and return an
64+
// instance of it in the map returned by Provider.GetResources.
65+
type ResourceType interface {
66+
// GetSchema returns the schema for this resource.
67+
GetSchema(context.Context) (Schema, diag.Diagnostics)
68+
69+
// NewResource instantiates a new Resource of this ResourceType.
70+
NewResource(context.Context, Provider) (Resource, diag.Diagnostics)
71+
}
72+
73+
// Resource represents a resource instance. This is the core interface that all
74+
// resources must implement.
75+
type Resource interface {
76+
// Create is called when the provider must create a new resource. Config
77+
// and planned state values should be read from the
78+
// CreateResourceRequest and new state values set on the
79+
// CreateResourceResponse.
80+
Create(context.Context, CreateResourceRequest, *CreateResourceResponse)
81+
82+
// Read is called when the provider must read resource values in order
83+
// to update state. Planned state values should be read from the
84+
// ReadResourceRequest and new state values set on the
85+
// ReadResourceResponse.
86+
Read(context.Context, ReadResourceRequest, *ReadResourceResponse)
87+
88+
// Update is called to update the state of the resource. Config, planned
89+
// state, and prior state values should be read from the
90+
// UpdateResourceRequest and new state values set on the
91+
// UpdateResourceResponse.
92+
Update(context.Context, UpdateResourceRequest, *UpdateResourceResponse)
93+
94+
// Delete is called when the provider must delete the resource. Config
95+
// values may be read from the DeleteResourceRequest.
96+
Delete(context.Context, DeleteResourceRequest, *DeleteResourceResponse)
97+
98+
// ImportState is called when the provider must import the resource.
99+
//
100+
// If import is not supported, it is recommended to use the
101+
// ResourceImportStateNotImplemented() call in this method.
102+
//
103+
// If setting an attribute with the import identifier, it is recommended
104+
// to use the ResourceImportStatePassthroughID() call in this method.
105+
ImportState(context.Context, ImportResourceStateRequest, *ImportResourceStateResponse)
106+
}
107+
```
108+
109+
## Prior Implementations
110+
111+
### terraform-plugin-sdk
112+
113+
The previous framework for provider implementations, Terraform Plugin SDK, can be found in the `terraform-plugin-sdk` repository. That framework has existed since the very early days of Terraform, where it was previously contained in a combined CLI and provider codebase, to support the code and testing aspects of provider development.
114+
115+
To implement managed resources and data sources, the previous framework was largely based around Go structure types and declarative definitions of intended behaviors. These were defined in the `helper/schema` package, in particular, the `Resource` type. The relevant fields are shown below.
116+
117+
```go
118+
type Resource struct {
119+
// ...
120+
121+
// SchemaVersion is the version number for this resource's Schema
122+
// definition. The current SchemaVersion stored in the state for each
123+
// resource. Provider authors can increment this version number
124+
// when Schema semantics change. If the State's SchemaVersion is less than
125+
// the current SchemaVersion, the InstanceState is yielded to the
126+
// MigrateState callback, where the provider can make whatever changes it
127+
// needs to update the state to be compatible to the latest version of the
128+
// Schema.
129+
//
130+
// When unset, SchemaVersion defaults to 0, so provider authors can start
131+
// their Versioning at any integer >= 1
132+
SchemaVersion int
133+
134+
// MigrateState is responsible for updating an InstanceState with an old
135+
// version to the format expected by the current version of the Schema.
136+
//
137+
// It is called during Refresh if the State's stored SchemaVersion is less
138+
// than the current SchemaVersion of the Resource.
139+
//
140+
// The function is yielded the state's stored SchemaVersion and a pointer to
141+
// the InstanceState that needs updating, as well as the configured
142+
// provider's configured meta interface{}, in case the migration process
143+
// needs to make any remote API calls.
144+
//
145+
// Deprecated: MigrateState is deprecated and any new changes to a resource's schema
146+
// should be handled by StateUpgraders. Existing MigrateState implementations
147+
// should remain for compatibility with existing state. MigrateState will
148+
// still be called if the stored SchemaVersion is less than the
149+
// first version of the StateUpgraders.
150+
MigrateState StateMigrateFunc
151+
152+
// StateUpgraders contains the functions responsible for upgrading an
153+
// existing state with an old schema version to a newer schema. It is
154+
// called specifically by Terraform when the stored schema version is less
155+
// than the current SchemaVersion of the Resource.
156+
//
157+
// StateUpgraders map specific schema versions to a StateUpgrader
158+
// function. The registered versions are expected to be ordered,
159+
// consecutive values. The initial value may be greater than 0 to account
160+
// for legacy schemas that weren't recorded and can be handled by
161+
// MigrateState.
162+
StateUpgraders []StateUpgrader
163+
}
164+
165+
type StateMigrateFunc func(int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
166+
167+
type StateUpgrader struct {
168+
// Version is the version schema that this Upgrader will handle, converting
169+
// it to Version+1.
170+
Version int
171+
172+
// Type describes the schema that this function can upgrade. Type is
173+
// required to decode the schema if the state was stored in a legacy
174+
// flatmap format.
175+
Type cty.Type
176+
177+
// Upgrade takes the JSON encoded state and the provider meta value, and
178+
// upgrades the state one single schema version. The provided state is
179+
// deocded into the default json types using a map[string]interface{}. It
180+
// is up to the StateUpgradeFunc to ensure that the returned value can be
181+
// encoded using the new schema.
182+
Upgrade StateUpgradeFunc
183+
}
184+
185+
// See StateUpgrader
186+
type StateUpgradeFunc func(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error)
187+
```
188+
189+
An example provider implementation:
190+
191+
```go
192+
func exampleResource() *schema.Resource {
193+
return &schema.Resource{
194+
// ... current schema ...
195+
196+
SchemaVersion: 1,
197+
StateUpgraders: []schema.StateUpgrader{
198+
{
199+
Type: exampleResourceSchemaV0().CoreConfigSchema().ImpliedType(),
200+
Upgrade: exampleResourceUpgradeV0,
201+
Version: 0,
202+
},
203+
},
204+
}
205+
}
206+
207+
func exampleResourceSchemaV0() *schema.Resource {
208+
return &schema.Resource{
209+
Schema: map[string]*schema.Schema{
210+
// ... previous schema ...
211+
},
212+
}
213+
}
214+
215+
func exampleResourceUpgradeV0(_ context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
216+
rawState["example_attribute"] = false
217+
218+
return rawState, nil
219+
}
220+
```
221+
222+
## Goals
223+
224+
Upgrade resource support in this framework should be:
225+
226+
- Able to support diagnostics, cancellation context, and the provider instance.
227+
- Available as exported functionality for provider developers.
228+
- Abstracted from terraform-plugin-go and convertable into those types to separate implementation concerns.
229+
- Ergonomic to implement Go code (e.g. have helper methods for common use cases).
230+
231+
Additional consideration should be given to:
232+
233+
- Whether provider implementations can use partial schemas and state information.
234+
- Whether schema versioning should be implemented within the existing resource design, outside of it, or wrap it.

0 commit comments

Comments
 (0)