Skip to content

Commit 4698f8a

Browse files
author
Paddy
authored
Add the ability to unmarshal RawState. (#42)
The tfprotov5.RawState type exposes state through a []byte containing JSON when upgrading resource state with the UpgradeResourceState RPC. We had not surfaced any JSON parsing or decoding in the exported API, so providers would need to parse the JSON themselves. To solve this, we use the same Unmarshal interface we use for parsing JSON from DynamicValues, letting users receive a tftypes.Value from a RawState without needing to worry about encoding... mostly. There is one hitch. The Flatmap syntax doesn't have a canonical translation (yet? As far as I know) to Terraform's type system. So we sidestep that by returning an error, for the moment. In the future, we could conceivably institute a mapping, though I'm not convinced it's a good idea to, given the number of assumptions that would need to be made. It is a larger undertaking to do that in a predictable, reliable way. Provider developers should provide their own mapping of Flatmap to tftypes.Value. The Flatmap property should only ever be populated in one specific scenario, as best I can tell: 1. The user _was_ using a version of Terraform below 0.12, and that is the last version of Terraform to write the state file. 2. The user upgrades to 0.12+ and obtains a new release of the provider 3. The new release of the provider is built on plugin-go At that point, the plugin-go-based provider would be the first version of the provider to write the state after 0.12, meaning it would be its responsibility to upgrade the state between the flatmap syntax and Terraform's type system. It is unknown how likely that scenario is, but I want to document it here to avoid any confusion.
1 parent a5b102f commit 4698f8a

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed

tfprotov5/state.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,25 @@
11
package tfprotov5
22

3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tftypes"
8+
)
9+
10+
// ErrUnknownRawStateType is returned when a RawState has no Flatmap or JSON
11+
// bytes set. This should never be returned during the normal operation of a
12+
// provider, and indicates one of the following:
13+
//
14+
// 1. terraform-plugin-go is out of sync with the protocol and should be
15+
// updated.
16+
//
17+
// 2. terrafrom-plugin-go has a bug.
18+
//
19+
// 3. The `RawState` was generated or modified by something other than
20+
// terraform-plugin-go and is no longer a valid value.
21+
var ErrUnknownRawStateType = errors.New("RawState had no JSON or flatmap data set")
22+
323
// RawState is the raw, undecoded state for providers to upgrade. It is
424
// undecoded as Terraform, for whatever reason, doesn't have the previous
525
// schema available to it, and so cannot decode the state itself and pushes
@@ -13,3 +33,47 @@ type RawState struct {
1333
JSON []byte
1434
Flatmap map[string]string
1535
}
36+
37+
// Unmarshal returns a `tftypes.Value` that represents the information
38+
// contained in the RawState in an easy-to-interact-with way. It is the
39+
// main purpose of the RawState type, and is how provider developers should
40+
// obtain state values from the UpgradeResourceState RPC call.
41+
//
42+
// Pass in the type you want the `Value` to be interpreted as. Terraform's type
43+
// system encodes in a lossy manner, meaning the type information is not
44+
// preserved losslessly when going over the wire. Sets, lists, and tuples all
45+
// look the same. Objects and maps all look the same, as well, as do
46+
// user-specified values when DynamicPseudoType is used in the schema.
47+
// Fortunately, the provider should already know the type; it should be the
48+
// type of the schema, or DynamicPseudoType if that's what's in the schema.
49+
// `Unmarshal` will then parse the value as though it belongs to that type, if
50+
// possible, and return a `tftypes.Value` with the appropriate information. If
51+
// the data can't be interpreted as that type, an error will be returned saying
52+
// so. In these cases, double check to make sure the schema is declaring the
53+
// same type being passed into `Unmarshal`.
54+
//
55+
// In the event an ErrUnknownRawStateType is returned, one of three things
56+
// has happened:
57+
//
58+
// 1. terraform-plugin-go is out of date and out of sync with the protocol, and
59+
// an issue should be opened on its repo to get it updated.
60+
//
61+
// 2. terraform-plugin-go has a bug somewhere, and an issue should be opened on
62+
// its repo to get it fixed.
63+
//
64+
// 3. The provider or a dependency has modified the `RawState` in an
65+
// unsupported way, or has created one from scratch, and should treat it as
66+
// opaque and not modify it, only calling `Unmarshal` on `RawState`s received
67+
// from RPC requests.
68+
//
69+
// State files written before Terraform 0.12 that haven't been upgraded yet
70+
// cannot be unmarshaled, and must have their Flatmap property read directly.
71+
func (s RawState) Unmarshal(typ tftypes.Type) (tftypes.Value, error) {
72+
if s.JSON != nil {
73+
return jsonUnmarshal(s.JSON, typ, tftypes.AttributePath{})
74+
}
75+
if s.Flatmap != nil {
76+
return tftypes.Value{}, fmt.Errorf("flatmap states cannot be unmarshaled, only states written by Terraform 0.12 and higher can be unmarshaled")
77+
}
78+
return tftypes.Value{}, ErrUnknownRawStateType
79+
}

0 commit comments

Comments
 (0)