mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-05-25 01:00:59 -04:00
This fixes a seemingly minor issue (GH-255) around plans showing changes when in fact there are none. But in reality this turned out to uncover a really terrible bug. The effect of what was happening was that multiple items in a set were being merged. Now, they were being merged in the right order, so if you didn't have rich types (lists in a set) then you never saw the effect since the later value would overwrite the earlier. But with lists (such as in security groups), you would end up with the lists merging. So, if you had one ingress rule with CIDR blocks and one with SGs, then after the merge both ingress rules would have BOTH CIDR and SGs, resulting in an incorrect plan (GH-255). This fixes the issue by introducing a `getSourceExact` bitflag to the ResourceData source. When this is set, ALL data must come from this level, instead of merging lower levels. In the case of sets and diffs, this is exactly what you want: "Get me the set 'foo' from the config and the config ONLY (not the state or diff or w/e)". Andddddd its fixed. GH-255
1117 lines
25 KiB
Go
1117 lines
25 KiB
Go
package schema
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// ResourceData is used to query and set the attributes of a resource.
|
|
//
|
|
// ResourceData is the primary argument received for CRUD operations on
|
|
// a resource as well as configuration of a provider. It is a powerful
|
|
// structure that can be used to not only query data, but check for changes,
|
|
// define partial state updates, etc.
|
|
//
|
|
// The most relevant methods to take a look at are Get, Set, and Partial.
|
|
type ResourceData struct {
|
|
// Settable (internally)
|
|
schema map[string]*Schema
|
|
config *terraform.ResourceConfig
|
|
state *terraform.InstanceState
|
|
diff *terraform.InstanceDiff
|
|
diffing bool
|
|
|
|
// Don't set
|
|
setMap map[string]string
|
|
newState *terraform.InstanceState
|
|
partial bool
|
|
partialMap map[string]struct{}
|
|
once sync.Once
|
|
}
|
|
|
|
// getSource represents the level we want to get for a value (internally).
|
|
// Any source less than or equal to the level will be loaded (whichever
|
|
// has a value first).
|
|
type getSource byte
|
|
|
|
const (
|
|
getSourceState getSource = 1 << iota
|
|
getSourceConfig
|
|
getSourceDiff
|
|
getSourceSet
|
|
getSourceExact
|
|
getSourceMax = getSourceSet
|
|
)
|
|
|
|
// getResult is the internal structure that is generated when a Get
|
|
// is called that contains some extra data that might be used.
|
|
type getResult struct {
|
|
Value interface{}
|
|
ValueProcessed interface{}
|
|
Computed bool
|
|
Exists bool
|
|
Schema *Schema
|
|
}
|
|
|
|
var getResultEmpty getResult
|
|
|
|
// Get returns the data for the given key, or nil if the key doesn't exist
|
|
// in the schema.
|
|
//
|
|
// If the key does exist in the schema but doesn't exist in the configuration,
|
|
// then the default value for that type will be returned. For strings, this is
|
|
// "", for numbers it is 0, etc.
|
|
//
|
|
// If you want to test if something is set at all in the configuration,
|
|
// use GetOk.
|
|
func (d *ResourceData) Get(key string) interface{} {
|
|
v, _ := d.GetOk(key)
|
|
return v
|
|
}
|
|
|
|
// GetChange returns the old and new value for a given key.
|
|
//
|
|
// HasChange should be used to check if a change exists. It is possible
|
|
// that both the old and new value are the same if the old value was not
|
|
// set and the new value is. This is common, for example, for boolean
|
|
// fields which have a zero value of false.
|
|
func (d *ResourceData) GetChange(key string) (interface{}, interface{}) {
|
|
o, n := d.getChange(key, getSourceConfig, getSourceDiff)
|
|
return o.Value, n.Value
|
|
}
|
|
|
|
// GetOk returns the data for the given key and whether or not the key
|
|
// existed or not in the configuration. The second boolean result will also
|
|
// be false if a key is given that isn't in the schema at all.
|
|
//
|
|
// The first result will not necessarilly be nil if the value doesn't exist.
|
|
// The second result should be checked to determine this information.
|
|
func (d *ResourceData) GetOk(key string) (interface{}, bool) {
|
|
r := d.getRaw(key, getSourceSet)
|
|
return r.Value, r.Exists
|
|
}
|
|
|
|
func (d *ResourceData) getRaw(key string, level getSource) getResult {
|
|
var parts []string
|
|
if key != "" {
|
|
parts = strings.Split(key, ".")
|
|
}
|
|
|
|
return d.getObject("", parts, d.schema, level)
|
|
}
|
|
|
|
// HasChange returns whether or not the given key has been changed.
|
|
func (d *ResourceData) HasChange(key string) bool {
|
|
o, n := d.GetChange(key)
|
|
return !reflect.DeepEqual(o, n)
|
|
}
|
|
|
|
// Partial turns partial state mode on/off.
|
|
//
|
|
// When partial state mode is enabled, then only key prefixes specified
|
|
// by SetPartial will be in the final state. This allows providers to return
|
|
// partial states for partially applied resources (when errors occur).
|
|
func (d *ResourceData) Partial(on bool) {
|
|
d.partial = on
|
|
if on {
|
|
if d.partialMap == nil {
|
|
d.partialMap = make(map[string]struct{})
|
|
}
|
|
} else {
|
|
d.partialMap = nil
|
|
}
|
|
}
|
|
|
|
// Set sets the value for the given key.
|
|
//
|
|
// If the key is invalid or the value is not a correct type, an error
|
|
// will be returned.
|
|
func (d *ResourceData) Set(key string, value interface{}) error {
|
|
if d.setMap == nil {
|
|
d.setMap = make(map[string]string)
|
|
}
|
|
|
|
parts := strings.Split(key, ".")
|
|
return d.setObject("", parts, d.schema, value)
|
|
}
|
|
|
|
// SetPartial adds the key prefix to the final state output while
|
|
// in partial state mode.
|
|
//
|
|
// If partial state mode is disabled, then this has no effect. Additionally,
|
|
// whenever partial state mode is toggled, the partial data is cleared.
|
|
func (d *ResourceData) SetPartial(k string) {
|
|
if d.partial {
|
|
d.partialMap[k] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Id returns the ID of the resource.
|
|
func (d *ResourceData) Id() string {
|
|
var result string
|
|
|
|
if d.state != nil {
|
|
result = d.state.ID
|
|
}
|
|
|
|
if d.newState != nil {
|
|
result = d.newState.ID
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// ConnInfo returns the connection info for this resource.
|
|
func (d *ResourceData) ConnInfo() map[string]string {
|
|
if d.newState != nil {
|
|
return d.newState.Ephemeral.ConnInfo
|
|
}
|
|
|
|
if d.state != nil {
|
|
return d.state.Ephemeral.ConnInfo
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetId sets the ID of the resource. If the value is blank, then the
|
|
// resource is destroyed.
|
|
func (d *ResourceData) SetId(v string) {
|
|
d.once.Do(d.init)
|
|
d.newState.ID = v
|
|
}
|
|
|
|
// SetConnInfo sets the connection info for a resource.
|
|
func (d *ResourceData) SetConnInfo(v map[string]string) {
|
|
d.once.Do(d.init)
|
|
d.newState.Ephemeral.ConnInfo = v
|
|
}
|
|
|
|
// State returns the new InstanceState after the diff and any Set
|
|
// calls.
|
|
func (d *ResourceData) State() *terraform.InstanceState {
|
|
var result terraform.InstanceState
|
|
result.ID = d.Id()
|
|
|
|
// If we have no ID, then this resource doesn't exist and we just
|
|
// return nil.
|
|
if result.ID == "" {
|
|
return nil
|
|
}
|
|
|
|
result.Attributes = d.stateObject("", d.schema)
|
|
result.Ephemeral.ConnInfo = d.ConnInfo()
|
|
|
|
if v := d.Id(); v != "" {
|
|
result.Attributes["id"] = d.Id()
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func (d *ResourceData) init() {
|
|
var copyState terraform.InstanceState
|
|
if d.state != nil {
|
|
copyState = *d.state
|
|
}
|
|
|
|
d.newState = ©State
|
|
}
|
|
|
|
func (d *ResourceData) diffChange(
|
|
k string) (interface{}, interface{}, bool, bool) {
|
|
// Get the change between the state and the config.
|
|
o, n := d.getChange(k, getSourceState, getSourceConfig|getSourceExact)
|
|
if !o.Exists {
|
|
o.Value = nil
|
|
}
|
|
if !n.Exists {
|
|
n.Value = nil
|
|
}
|
|
|
|
// Return the old, new, and whether there is a change
|
|
return o.Value, n.Value, !reflect.DeepEqual(o.Value, n.Value), n.Computed
|
|
}
|
|
|
|
func (d *ResourceData) getChange(
|
|
key string,
|
|
oldLevel getSource,
|
|
newLevel getSource) (getResult, getResult) {
|
|
var parts, parts2 []string
|
|
if key != "" {
|
|
parts = strings.Split(key, ".")
|
|
parts2 = strings.Split(key, ".")
|
|
}
|
|
|
|
o := d.getObject("", parts, d.schema, oldLevel)
|
|
n := d.getObject("", parts2, d.schema, newLevel)
|
|
return o, n
|
|
}
|
|
|
|
func (d *ResourceData) get(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
source getSource) getResult {
|
|
switch schema.Type {
|
|
case TypeList:
|
|
return d.getList(k, parts, schema, source)
|
|
case TypeMap:
|
|
return d.getMap(k, parts, schema, source)
|
|
case TypeSet:
|
|
return d.getSet(k, parts, schema, source)
|
|
case TypeBool:
|
|
fallthrough
|
|
case TypeInt:
|
|
fallthrough
|
|
case TypeString:
|
|
return d.getPrimitive(k, parts, schema, source)
|
|
default:
|
|
panic(fmt.Sprintf("%s: unknown type %#v", k, schema.Type))
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) getSet(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
source getSource) getResult {
|
|
s := &Set{F: schema.Set}
|
|
result := getResult{Schema: schema, Value: s}
|
|
|
|
// Get the list. For sets, the entire source must be exact: the
|
|
// entire set must come from set, diff, state, etc. So we go backwards
|
|
// and once we get a result, we take it. Or, we never get a result.
|
|
var raw getResult
|
|
for listSource := source; listSource > 0; listSource >>= 1 {
|
|
if source&getSourceExact != 0 && listSource != source {
|
|
break
|
|
}
|
|
|
|
raw = d.getList(k, nil, schema, listSource|getSourceExact)
|
|
if raw.Exists {
|
|
break
|
|
}
|
|
}
|
|
if !raw.Exists {
|
|
if len(parts) > 0 {
|
|
return d.getList(k, parts, schema, source)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// If the entire list is computed, then the entire set is
|
|
// necessarilly computed.
|
|
if raw.Computed {
|
|
result.Computed = true
|
|
return result
|
|
}
|
|
|
|
list := raw.Value.([]interface{})
|
|
if len(list) == 0 {
|
|
if len(parts) > 0 {
|
|
return d.getList(k, parts, schema, source)
|
|
}
|
|
|
|
result.Exists = raw.Exists
|
|
return result
|
|
}
|
|
|
|
// This is a reverse map of hash code => index in config used to
|
|
// resolve direct set item lookup for turning into state. Confused?
|
|
// Read on...
|
|
//
|
|
// To create the state (the state* functions), a Get call is done
|
|
// with a full key such as "ports.0". The index of a set ("0") doesn't
|
|
// make a lot of sense, but we need to deterministically list out
|
|
// elements of the set like this. Luckily, same sets have a deterministic
|
|
// List() output, so we can use that to look things up.
|
|
//
|
|
// This mapping makes it so that we can look up the hash code of an
|
|
// object back to its index in the REAL config.
|
|
var indexMap map[int]int
|
|
if len(parts) > 0 {
|
|
indexMap = make(map[int]int)
|
|
}
|
|
|
|
// Build the set from all the items using the given hash code
|
|
for i, v := range list {
|
|
code := s.add(v)
|
|
if indexMap != nil {
|
|
indexMap[code] = i
|
|
}
|
|
}
|
|
|
|
// If we're trying to get a specific element, then rewrite the
|
|
// index to be just that, then jump direct to getList.
|
|
if len(parts) > 0 {
|
|
index := parts[0]
|
|
indexInt, err := strconv.ParseInt(index, 0, 0)
|
|
if err != nil {
|
|
return getResultEmpty
|
|
}
|
|
|
|
codes := s.listCode()
|
|
if int(indexInt) >= len(codes) {
|
|
return getResultEmpty
|
|
}
|
|
code := codes[indexInt]
|
|
realIndex := indexMap[code]
|
|
|
|
parts[0] = strconv.FormatInt(int64(realIndex), 10)
|
|
return d.getList(k, parts, schema, source)
|
|
}
|
|
|
|
result.Exists = true
|
|
return result
|
|
}
|
|
|
|
func (d *ResourceData) getMap(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
source getSource) getResult {
|
|
elemSchema := &Schema{Type: TypeString}
|
|
|
|
result := make(map[string]interface{})
|
|
resultSet := false
|
|
prefix := k + "."
|
|
|
|
exact := source&getSourceExact != 0
|
|
source &^= getSourceExact
|
|
|
|
if !exact || source == getSourceState {
|
|
if d.state != nil && source >= getSourceState {
|
|
for k, _ := range d.state.Attributes {
|
|
if !strings.HasPrefix(k, prefix) {
|
|
continue
|
|
}
|
|
|
|
single := k[len(prefix):]
|
|
result[single] = d.getPrimitive(k, nil, elemSchema, source).Value
|
|
resultSet = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if d.config != nil && source == getSourceConfig {
|
|
// For config, we always set the result to exactly what was requested
|
|
if mraw, ok := d.config.Get(k); ok {
|
|
result = make(map[string]interface{})
|
|
switch m := mraw.(type) {
|
|
case []interface{}:
|
|
for _, innerRaw := range m {
|
|
for k, v := range innerRaw.(map[string]interface{}) {
|
|
result[k] = v
|
|
}
|
|
}
|
|
|
|
resultSet = true
|
|
case []map[string]interface{}:
|
|
for _, innerRaw := range m {
|
|
for k, v := range innerRaw {
|
|
result[k] = v
|
|
}
|
|
}
|
|
|
|
resultSet = true
|
|
case map[string]interface{}:
|
|
result = m
|
|
resultSet = true
|
|
default:
|
|
panic(fmt.Sprintf("unknown type: %#v", mraw))
|
|
}
|
|
} else {
|
|
result = nil
|
|
}
|
|
}
|
|
|
|
if !exact || source == getSourceDiff {
|
|
if d.diff != nil && source >= getSourceDiff {
|
|
for k, v := range d.diff.Attributes {
|
|
if !strings.HasPrefix(k, prefix) {
|
|
continue
|
|
}
|
|
resultSet = true
|
|
|
|
single := k[len(prefix):]
|
|
|
|
if v.NewRemoved {
|
|
delete(result, single)
|
|
} else {
|
|
result[single] = d.getPrimitive(k, nil, elemSchema, source).Value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !exact || source == getSourceSet {
|
|
if d.setMap != nil && source >= getSourceSet {
|
|
cleared := false
|
|
for k, _ := range d.setMap {
|
|
if !strings.HasPrefix(k, prefix) {
|
|
continue
|
|
}
|
|
resultSet = true
|
|
|
|
if !cleared {
|
|
// We clear the results if they are in the set map
|
|
result = make(map[string]interface{})
|
|
cleared = true
|
|
}
|
|
|
|
single := k[len(prefix):]
|
|
result[single] = d.getPrimitive(k, nil, elemSchema, source).Value
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're requesting a specific element, return that
|
|
var resultValue interface{} = result
|
|
if len(parts) > 0 {
|
|
resultValue = result[parts[0]]
|
|
}
|
|
|
|
return getResult{
|
|
Value: resultValue,
|
|
Exists: resultSet,
|
|
Schema: schema,
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) getObject(
|
|
k string,
|
|
parts []string,
|
|
schema map[string]*Schema,
|
|
source getSource) getResult {
|
|
if len(parts) > 0 {
|
|
// We're requesting a specific key in an object
|
|
key := parts[0]
|
|
parts = parts[1:]
|
|
s, ok := schema[key]
|
|
if !ok {
|
|
return getResultEmpty
|
|
}
|
|
|
|
if k != "" {
|
|
// If we're not at the root, then we need to append
|
|
// the key to get the full key path.
|
|
key = fmt.Sprintf("%s.%s", k, key)
|
|
}
|
|
|
|
return d.get(key, parts, s, source)
|
|
}
|
|
|
|
// Get the entire object
|
|
result := make(map[string]interface{})
|
|
for field, _ := range schema {
|
|
result[field] = d.getObject(k, []string{field}, schema, source).Value
|
|
}
|
|
|
|
return getResult{
|
|
Value: result,
|
|
Exists: true,
|
|
Schema: &Schema{
|
|
Elem: schema,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) getList(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
source getSource) getResult {
|
|
if len(parts) > 0 {
|
|
// We still have parts left over meaning we're accessing an
|
|
// element of this list.
|
|
idx := parts[0]
|
|
parts = parts[1:]
|
|
|
|
// Special case if we're accessing the count of the list
|
|
if idx == "#" {
|
|
schema := &Schema{Type: TypeInt}
|
|
return d.get(k+".#", parts, schema, source)
|
|
}
|
|
|
|
key := fmt.Sprintf("%s.%s", k, idx)
|
|
switch t := schema.Elem.(type) {
|
|
case *Resource:
|
|
return d.getObject(key, parts, t.Schema, source)
|
|
case *Schema:
|
|
return d.get(key, parts, t, source)
|
|
}
|
|
}
|
|
|
|
// Get the entire list.
|
|
var result []interface{}
|
|
count := d.getList(k, []string{"#"}, schema, source)
|
|
if !count.Computed {
|
|
result = make([]interface{}, count.Value.(int))
|
|
for i, _ := range result {
|
|
is := strconv.FormatInt(int64(i), 10)
|
|
result[i] = d.getList(k, []string{is}, schema, source).Value
|
|
}
|
|
}
|
|
|
|
return getResult{
|
|
Value: result,
|
|
Computed: count.Computed,
|
|
Exists: count.Exists,
|
|
Schema: schema,
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) getPrimitive(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
source getSource) getResult {
|
|
var result string
|
|
var resultProcessed interface{}
|
|
var resultComputed, resultSet bool
|
|
exact := source&getSourceExact != 0
|
|
source &^= getSourceExact
|
|
|
|
if !exact || source == getSourceState {
|
|
if d.state != nil && source >= getSourceState {
|
|
result, resultSet = d.state.Attributes[k]
|
|
}
|
|
}
|
|
|
|
// No exact check is needed here because config is always exact
|
|
if d.config != nil && source == getSourceConfig {
|
|
// For config, we always return the exact value
|
|
if v, ok := d.config.Get(k); ok {
|
|
if err := mapstructure.WeakDecode(v, &result); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
resultSet = true
|
|
} else {
|
|
result = ""
|
|
resultSet = false
|
|
}
|
|
|
|
// If it is computed, set that.
|
|
resultComputed = d.config.IsComputed(k)
|
|
}
|
|
|
|
if !exact || source == getSourceDiff {
|
|
if d.diff != nil && source >= getSourceDiff {
|
|
attrD, ok := d.diff.Attributes[k]
|
|
if ok {
|
|
if !attrD.NewComputed {
|
|
result = attrD.New
|
|
if attrD.NewExtra != nil {
|
|
// If NewExtra != nil, then we have processed data as the New,
|
|
// so we store that but decode the unprocessed data into result
|
|
resultProcessed = result
|
|
|
|
err := mapstructure.WeakDecode(attrD.NewExtra, &result)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
resultSet = true
|
|
} else {
|
|
result = ""
|
|
resultSet = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if !exact || source == getSourceSet {
|
|
if d.setMap != nil && source >= getSourceSet {
|
|
if v, ok := d.setMap[k]; ok {
|
|
result = v
|
|
resultSet = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !resultSet {
|
|
result = ""
|
|
}
|
|
|
|
var resultValue interface{}
|
|
switch schema.Type {
|
|
case TypeBool:
|
|
if result == "" {
|
|
resultValue = false
|
|
break
|
|
}
|
|
|
|
v, err := strconv.ParseBool(result)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
resultValue = v
|
|
case TypeString:
|
|
// Use the value as-is. We just put this case here to be explicit.
|
|
resultValue = result
|
|
case TypeInt:
|
|
if result == "" {
|
|
resultValue = 0
|
|
break
|
|
}
|
|
|
|
if resultComputed {
|
|
break
|
|
}
|
|
|
|
v, err := strconv.ParseInt(result, 0, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
resultValue = int(v)
|
|
default:
|
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
|
}
|
|
|
|
return getResult{
|
|
Value: resultValue,
|
|
ValueProcessed: resultProcessed,
|
|
Computed: resultComputed,
|
|
Exists: resultSet,
|
|
Schema: schema,
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) set(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
value interface{}) error {
|
|
switch schema.Type {
|
|
case TypeList:
|
|
return d.setList(k, parts, schema, value)
|
|
case TypeMap:
|
|
return d.setMapValue(k, parts, schema, value)
|
|
case TypeSet:
|
|
return d.setSet(k, parts, schema, value)
|
|
case TypeBool:
|
|
fallthrough
|
|
case TypeInt:
|
|
fallthrough
|
|
case TypeString:
|
|
return d.setPrimitive(k, schema, value)
|
|
default:
|
|
panic(fmt.Sprintf("%s: unknown type %#v", k, schema.Type))
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) setList(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
value interface{}) error {
|
|
if len(parts) > 0 {
|
|
// We're setting a specific element
|
|
idx := parts[0]
|
|
parts = parts[1:]
|
|
|
|
// Special case if we're accessing the count of the list
|
|
if idx == "#" {
|
|
return fmt.Errorf("%s: can't set count of list", k)
|
|
}
|
|
|
|
key := fmt.Sprintf("%s.%s", k, idx)
|
|
switch t := schema.Elem.(type) {
|
|
case *Resource:
|
|
return d.setObject(key, parts, t.Schema, value)
|
|
case *Schema:
|
|
return d.set(key, parts, t, value)
|
|
}
|
|
}
|
|
|
|
var vs []interface{}
|
|
if err := mapstructure.Decode(value, &vs); err != nil {
|
|
return fmt.Errorf("%s: %s", k, err)
|
|
}
|
|
|
|
// Set the entire list.
|
|
var err error
|
|
for i, elem := range vs {
|
|
is := strconv.FormatInt(int64(i), 10)
|
|
err = d.setList(k, []string{is}, schema, elem)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
for i, _ := range vs {
|
|
is := strconv.FormatInt(int64(i), 10)
|
|
d.setList(k, []string{is}, schema, nil)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
d.setMap[k+".#"] = strconv.FormatInt(int64(len(vs)), 10)
|
|
return nil
|
|
}
|
|
|
|
func (d *ResourceData) setMapValue(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
value interface{}) error {
|
|
elemSchema := &Schema{Type: TypeString}
|
|
if len(parts) > 0 {
|
|
return fmt.Errorf("%s: full map must be set, no a single element", k)
|
|
}
|
|
|
|
// Delete any prior map set
|
|
/*
|
|
v := d.getMap(k, nil, schema, getSourceSet)
|
|
for subKey, _ := range v.(map[string]interface{}) {
|
|
delete(d.setMap, fmt.Sprintf("%s.%s", k, subKey))
|
|
}
|
|
*/
|
|
|
|
v := reflect.ValueOf(value)
|
|
if v.Kind() != reflect.Map {
|
|
return fmt.Errorf("%s: must be a map", k)
|
|
}
|
|
if v.Type().Key().Kind() != reflect.String {
|
|
return fmt.Errorf("%s: keys must strings", k)
|
|
}
|
|
vs := make(map[string]interface{})
|
|
for _, mk := range v.MapKeys() {
|
|
mv := v.MapIndex(mk)
|
|
vs[mk.String()] = mv.Interface()
|
|
}
|
|
|
|
for subKey, v := range vs {
|
|
err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *ResourceData) setObject(
|
|
k string,
|
|
parts []string,
|
|
schema map[string]*Schema,
|
|
value interface{}) error {
|
|
if len(parts) > 0 {
|
|
// We're setting a specific key in an object
|
|
key := parts[0]
|
|
parts = parts[1:]
|
|
|
|
s, ok := schema[key]
|
|
if !ok {
|
|
return fmt.Errorf("%s (internal): unknown key to set: %s", k, key)
|
|
}
|
|
|
|
if k != "" {
|
|
// If we're not at the root, then we need to append
|
|
// the key to get the full key path.
|
|
key = fmt.Sprintf("%s.%s", k, key)
|
|
}
|
|
|
|
return d.set(key, parts, s, value)
|
|
}
|
|
|
|
// Set the entire object. First decode into a proper structure
|
|
var v map[string]interface{}
|
|
if err := mapstructure.Decode(value, &v); err != nil {
|
|
return fmt.Errorf("%s: %s", k, err)
|
|
}
|
|
|
|
// Set each element in turn
|
|
var err error
|
|
for k1, v1 := range v {
|
|
err = d.setObject(k, []string{k1}, schema, v1)
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
for k1, _ := range v {
|
|
d.setObject(k, []string{k1}, schema, nil)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (d *ResourceData) setPrimitive(
|
|
k string,
|
|
schema *Schema,
|
|
v interface{}) error {
|
|
if v == nil {
|
|
delete(d.setMap, k)
|
|
return nil
|
|
}
|
|
|
|
var set string
|
|
switch schema.Type {
|
|
case TypeBool:
|
|
var b bool
|
|
if err := mapstructure.Decode(v, &b); err != nil {
|
|
return fmt.Errorf("%s: %s", k, err)
|
|
}
|
|
|
|
set = strconv.FormatBool(b)
|
|
case TypeString:
|
|
if err := mapstructure.Decode(v, &set); err != nil {
|
|
return fmt.Errorf("%s: %s", k, err)
|
|
}
|
|
case TypeInt:
|
|
var n int
|
|
if err := mapstructure.Decode(v, &n); err != nil {
|
|
return fmt.Errorf("%s: %s", k, err)
|
|
}
|
|
|
|
set = strconv.FormatInt(int64(n), 10)
|
|
default:
|
|
return fmt.Errorf("Unknown type: %#v", schema.Type)
|
|
}
|
|
|
|
d.setMap[k] = set
|
|
return nil
|
|
}
|
|
|
|
func (d *ResourceData) setSet(
|
|
k string,
|
|
parts []string,
|
|
schema *Schema,
|
|
value interface{}) error {
|
|
if len(parts) > 0 {
|
|
return fmt.Errorf("%s: can only set the full set, not elements", k)
|
|
}
|
|
|
|
// If it is a slice, then we have to turn it into a *Set so that
|
|
// we get the proper order back based on the hash code.
|
|
if v := reflect.ValueOf(value); v.Kind() == reflect.Slice {
|
|
// Set the entire list, this lets us get sane values out of it
|
|
if err := d.setList(k, nil, schema, value); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build the set by going over the list items in order and
|
|
// hashing them into the set. The reason we go over the list and
|
|
// not the `value` directly is because this forces all types
|
|
// to become []interface{} (generic) instead of []string, which
|
|
// most hash functions are expecting.
|
|
s := &Set{F: schema.Set}
|
|
source := getSourceSet | getSourceExact
|
|
for i := 0; i < v.Len(); i++ {
|
|
is := strconv.FormatInt(int64(i), 10)
|
|
result := d.getList(k, []string{is}, schema, source)
|
|
if !result.Exists {
|
|
panic("just set item doesn't exist")
|
|
}
|
|
|
|
s.Add(result.Value)
|
|
}
|
|
|
|
value = s
|
|
}
|
|
|
|
if s, ok := value.(*Set); ok {
|
|
value = s.List()
|
|
}
|
|
|
|
return d.setList(k, nil, schema, value)
|
|
}
|
|
|
|
func (d *ResourceData) stateList(
|
|
prefix string,
|
|
schema *Schema) map[string]string {
|
|
countRaw := d.get(prefix, []string{"#"}, schema, d.stateSource(prefix))
|
|
if !countRaw.Exists {
|
|
if schema.Computed {
|
|
// If it is computed, then it always _exists_ in the state,
|
|
// it is just empty.
|
|
countRaw.Exists = true
|
|
countRaw.Value = 0
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
count := countRaw.Value.(int)
|
|
|
|
result := make(map[string]string)
|
|
if count > 0 || schema.Computed {
|
|
result[prefix+".#"] = strconv.FormatInt(int64(count), 10)
|
|
}
|
|
for i := 0; i < count; i++ {
|
|
key := fmt.Sprintf("%s.%d", prefix, i)
|
|
|
|
var m map[string]string
|
|
switch t := schema.Elem.(type) {
|
|
case *Resource:
|
|
m = d.stateObject(key, t.Schema)
|
|
case *Schema:
|
|
m = d.stateSingle(key, t)
|
|
}
|
|
|
|
for k, v := range m {
|
|
result[k] = v
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (d *ResourceData) stateMap(
|
|
prefix string,
|
|
schema *Schema) map[string]string {
|
|
v := d.getMap(prefix, nil, schema, d.stateSource(prefix))
|
|
if !v.Exists {
|
|
return nil
|
|
}
|
|
|
|
elemSchema := &Schema{Type: TypeString}
|
|
result := make(map[string]string)
|
|
for mk, _ := range v.Value.(map[string]interface{}) {
|
|
mp := fmt.Sprintf("%s.%s", prefix, mk)
|
|
for k, v := range d.stateSingle(mp, elemSchema) {
|
|
result[k] = v
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (d *ResourceData) stateObject(
|
|
prefix string,
|
|
schema map[string]*Schema) map[string]string {
|
|
result := make(map[string]string)
|
|
for k, v := range schema {
|
|
key := k
|
|
if prefix != "" {
|
|
key = prefix + "." + key
|
|
}
|
|
|
|
for k1, v1 := range d.stateSingle(key, v) {
|
|
result[k1] = v1
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (d *ResourceData) statePrimitive(
|
|
prefix string,
|
|
schema *Schema) map[string]string {
|
|
raw := d.getRaw(prefix, d.stateSource(prefix))
|
|
if !raw.Exists {
|
|
return nil
|
|
}
|
|
|
|
v := raw.Value
|
|
if raw.ValueProcessed != nil {
|
|
v = raw.ValueProcessed
|
|
}
|
|
|
|
var vs string
|
|
switch schema.Type {
|
|
case TypeBool:
|
|
vs = strconv.FormatBool(v.(bool))
|
|
case TypeString:
|
|
vs = v.(string)
|
|
case TypeInt:
|
|
vs = strconv.FormatInt(int64(v.(int)), 10)
|
|
default:
|
|
panic(fmt.Sprintf("Unknown type: %#v", schema.Type))
|
|
}
|
|
|
|
return map[string]string{
|
|
prefix: vs,
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) stateSet(
|
|
prefix string,
|
|
schema *Schema) map[string]string {
|
|
raw := d.get(prefix, nil, schema, d.stateSource(prefix))
|
|
if !raw.Exists {
|
|
if schema.Computed {
|
|
// If it is computed, then it always _exists_ in the state,
|
|
// it is just empty.
|
|
raw.Exists = true
|
|
raw.Value = new(Set)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
set := raw.Value.(*Set)
|
|
list := set.List()
|
|
result := make(map[string]string)
|
|
result[prefix+".#"] = strconv.FormatInt(int64(len(list)), 10)
|
|
for i := 0; i < len(list); i++ {
|
|
key := fmt.Sprintf("%s.%d", prefix, i)
|
|
|
|
var m map[string]string
|
|
switch t := schema.Elem.(type) {
|
|
case *Resource:
|
|
m = d.stateObject(key, t.Schema)
|
|
case *Schema:
|
|
m = d.stateSingle(key, t)
|
|
}
|
|
|
|
for k, v := range m {
|
|
result[k] = v
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (d *ResourceData) stateSingle(
|
|
prefix string,
|
|
schema *Schema) map[string]string {
|
|
switch schema.Type {
|
|
case TypeList:
|
|
return d.stateList(prefix, schema)
|
|
case TypeMap:
|
|
return d.stateMap(prefix, schema)
|
|
case TypeSet:
|
|
return d.stateSet(prefix, schema)
|
|
case TypeBool:
|
|
fallthrough
|
|
case TypeInt:
|
|
fallthrough
|
|
case TypeString:
|
|
return d.statePrimitive(prefix, schema)
|
|
default:
|
|
panic(fmt.Sprintf("%s: unknown type %#v", prefix, schema.Type))
|
|
}
|
|
}
|
|
|
|
func (d *ResourceData) stateSource(prefix string) getSource {
|
|
// If we're not doing a partial apply, then get the set level
|
|
if !d.partial {
|
|
return getSourceSet
|
|
}
|
|
|
|
// Otherwise, only return getSourceSet if its in the partial map.
|
|
// Otherwise we use state level only.
|
|
for k, _ := range d.partialMap {
|
|
if strings.HasPrefix(prefix, k) {
|
|
return getSourceSet
|
|
}
|
|
}
|
|
|
|
return getSourceState
|
|
}
|