terraform: InstanceState.Meta is value type interface{}

This changes the type of values in Meta for InstanceState to
`interface{}`. They were `string` before.

This will allow richer structures to be persisted to this without
flatmapping them (down with flatmap!). The documentation clearly states
that only primitives/collections are allowed here.

The only thing using this was helper/schema for schema versioning.
Appropriate type checking was added to make this change safe.

The timeout work @catsby is doing will use this for a richer structure.
This commit is contained in:
Mitchell Hashimoto 2017-02-23 10:44:05 -08:00
parent 9379ec9c60
commit 3342aa580c
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
6 changed files with 45 additions and 23 deletions

View File

@ -353,7 +353,7 @@ func (r *Resource) Data(s *terraform.InstanceState) *ResourceData {
} }
// Set the schema version to latest by default // Set the schema version to latest by default
result.meta = map[string]string{ result.meta = map[string]interface{}{
"schema_version": strconv.Itoa(r.SchemaVersion), "schema_version": strconv.Itoa(r.SchemaVersion),
} }
@ -378,7 +378,22 @@ func (r *Resource) isTopLevel() bool {
// Determines if a given InstanceState needs to be migrated by checking the // Determines if a given InstanceState needs to be migrated by checking the
// stored version number with the current SchemaVersion // stored version number with the current SchemaVersion
func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
stateSchemaVersion, _ := strconv.Atoi(is.Meta["schema_version"]) // Get the raw interface{} value for the schema version. If it doesn't
// exist or is nil then set it to zero.
raw := is.Meta["schema_version"]
if raw == nil {
raw = "0"
}
// Try to convert it to a string. If it isn't a string then we pretend
// that it isn't set at all. It should never not be a string unless it
// was manually tampered with.
rawString, ok := raw.(string)
if !ok {
rawString = "0"
}
stateSchemaVersion, _ := strconv.Atoi(rawString)
return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion
} }
@ -386,7 +401,7 @@ func (r *Resource) recordCurrentSchemaVersion(
state *terraform.InstanceState) *terraform.InstanceState { state *terraform.InstanceState) *terraform.InstanceState {
if state != nil && r.SchemaVersion > 0 { if state != nil && r.SchemaVersion > 0 {
if state.Meta == nil { if state.Meta == nil {
state.Meta = make(map[string]string) state.Meta = make(map[string]interface{})
} }
state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion)
} }

View File

@ -23,7 +23,7 @@ type ResourceData struct {
config *terraform.ResourceConfig config *terraform.ResourceConfig
state *terraform.InstanceState state *terraform.InstanceState
diff *terraform.InstanceDiff diff *terraform.InstanceDiff
meta map[string]string meta map[string]interface{}
// Don't set // Don't set
multiReader *MultiLevelFieldReader multiReader *MultiLevelFieldReader

View File

@ -52,7 +52,7 @@ func TestResourceApply_create(t *testing.T) {
"id": "foo", "id": "foo",
"foo": "42", "foo": "42",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
} }
@ -210,7 +210,7 @@ func TestResourceApply_destroyPartial(t *testing.T) {
"id": "bar", "id": "bar",
"foo": "42", "foo": "42",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "3", "schema_version": "3",
}, },
} }
@ -558,7 +558,7 @@ func TestResourceRefresh(t *testing.T) {
"id": "bar", "id": "bar",
"foo": "13", "foo": "13",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
} }
@ -749,7 +749,7 @@ func TestResourceRefresh_needsMigration(t *testing.T) {
Attributes: map[string]string{ Attributes: map[string]string{
"oldfoo": "1.2", "oldfoo": "1.2",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "1", "schema_version": "1",
}, },
} }
@ -765,7 +765,7 @@ func TestResourceRefresh_needsMigration(t *testing.T) {
"id": "bar", "id": "bar",
"newfoo": "13", "newfoo": "13",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
} }
@ -803,7 +803,7 @@ func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
Attributes: map[string]string{ Attributes: map[string]string{
"newfoo": "12", "newfoo": "12",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
} }
@ -819,7 +819,7 @@ func TestResourceRefresh_noMigrationNeeded(t *testing.T) {
"id": "bar", "id": "bar",
"newfoo": "13", "newfoo": "13",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
} }
@ -871,7 +871,7 @@ func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) {
"id": "bar", "id": "bar",
"newfoo": "13", "newfoo": "13",
}, },
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "1", "schema_version": "1",
}, },
} }
@ -945,7 +945,7 @@ func TestResourceData(t *testing.T) {
} }
// Set expectations // Set expectations
state.Meta = map[string]string{ state.Meta = map[string]interface{}{
"schema_version": "2", "schema_version": "2",
} }

View File

@ -1541,8 +1541,9 @@ type InstanceState struct {
// Meta is a simple K/V map that is persisted to the State but otherwise // Meta is a simple K/V map that is persisted to the State but otherwise
// ignored by Terraform core. It's meant to be used for accounting by // ignored by Terraform core. It's meant to be used for accounting by
// external client code. // external client code. The value here must only contain Go primitives
Meta map[string]string `json:"meta"` // and collections.
Meta map[string]interface{} `json:"meta"`
// Tainted is used to mark a resource for recreation. // Tainted is used to mark a resource for recreation.
Tainted bool `json:"tainted"` Tainted bool `json:"tainted"`
@ -1561,7 +1562,7 @@ func (s *InstanceState) init() {
s.Attributes = make(map[string]string) s.Attributes = make(map[string]string)
} }
if s.Meta == nil { if s.Meta == nil {
s.Meta = make(map[string]string) s.Meta = make(map[string]interface{})
} }
s.Ephemeral.init() s.Ephemeral.init()
} }

View File

@ -278,7 +278,7 @@ func TestStateDeepCopy(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{}, Meta: map[string]interface{}{},
}, },
}, },
}, },
@ -298,7 +298,7 @@ func TestStateDeepCopy(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{}, Meta: map[string]interface{}{},
}, },
Deposed: []*InstanceState{ Deposed: []*InstanceState{
{ID: "test"}, {ID: "test"},
@ -389,7 +389,7 @@ func TestStateEqual(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "1", "schema_version": "1",
}, },
}, },
@ -405,7 +405,7 @@ func TestStateEqual(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "2", "schema_version": "2",
}, },
}, },
@ -610,7 +610,7 @@ func TestStateIncrementSerialMaybe(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{}, Meta: map[string]interface{}{},
}, },
}, },
}, },
@ -625,7 +625,7 @@ func TestStateIncrementSerialMaybe(t *testing.T) {
Resources: map[string]*ResourceState{ Resources: map[string]*ResourceState{
"test_instance.foo": &ResourceState{ "test_instance.foo": &ResourceState{
Primary: &InstanceState{ Primary: &InstanceState{
Meta: map[string]string{ Meta: map[string]interface{}{
"schema_version": "1", "schema_version": "1",
}, },
}, },

View File

@ -150,16 +150,22 @@ func (old *instanceStateV1) upgradeToV2() (*InstanceState, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err) return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
} }
meta, err := copystructure.Copy(old.Meta) meta, err := copystructure.Copy(old.Meta)
if err != nil { if err != nil {
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err) return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
} }
newMeta := make(map[string]interface{})
for k, v := range meta.(map[string]string) {
newMeta[k] = v
}
return &InstanceState{ return &InstanceState{
ID: old.ID, ID: old.ID,
Attributes: attributes.(map[string]string), Attributes: attributes.(map[string]string),
Ephemeral: *ephemeral, Ephemeral: *ephemeral,
Meta: meta.(map[string]string), Meta: newMeta,
}, nil }, nil
} }