aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/flatmap/expand.go
blob: 1449065e9d84efe3baab5cd470d1befa872c104d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package flatmap

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/hashicorp/hil"
)

// Expand takes a map and a key (prefix) and expands that value into
// a more complex structure. This is the reverse of the Flatten operation.
func Expand(m map[string]string, key string) interface{} {
	// If the key is exactly a key in the map, just return it
	if v, ok := m[key]; ok {
		if v == "true" {
			return true
		} else if v == "false" {
			return false
		}

		return v
	}

	// Check if the key is an array, and if so, expand the array
	if v, ok := m[key+".#"]; ok {
		// If the count of the key is unknown, then just put the unknown
		// value in the value itself. This will be detected by Terraform
		// core later.
		if v == hil.UnknownValue {
			return v
		}

		return expandArray(m, key)
	}

	// Check if this is a prefix in the map
	prefix := key + "."
	for k := range m {
		if strings.HasPrefix(k, prefix) {
			return expandMap(m, prefix)
		}
	}

	return nil
}

func expandArray(m map[string]string, prefix string) []interface{} {
	num, err := strconv.ParseInt(m[prefix+".#"], 0, 0)
	if err != nil {
		panic(err)
	}

	// If the number of elements in this array is 0, then return an
	// empty slice as there is nothing to expand. Trying to expand it
	// anyway could lead to crashes as any child maps, arrays or sets
	// that no longer exist are still shown as empty with a count of 0.
	if num == 0 {
		return []interface{}{}
	}

	// NOTE: "num" is not necessarily accurate, e.g. if a user tampers
	// with state, so the following code should not crash when given a
	// number of items more or less than what's given in num. The
	// num key is mainly just a hint that this is a list or set.

	// The Schema "Set" type stores its values in an array format, but
	// using numeric hash values instead of ordinal keys. Take the set
	// of keys regardless of value, and expand them in numeric order.
	// See GH-11042 for more details.
	keySet := map[int]bool{}
	computed := map[string]bool{}
	for k := range m {
		if !strings.HasPrefix(k, prefix+".") {
			continue
		}

		key := k[len(prefix)+1:]
		idx := strings.Index(key, ".")
		if idx != -1 {
			key = key[:idx]
		}

		// skip the count value
		if key == "#" {
			continue
		}

		// strip the computed flag if there is one
		if strings.HasPrefix(key, "~") {
			key = key[1:]
			computed[key] = true
		}

		k, err := strconv.Atoi(key)
		if err != nil {
			panic(err)
		}
		keySet[int(k)] = true
	}

	keysList := make([]int, 0, num)
	for key := range keySet {
		keysList = append(keysList, key)
	}
	sort.Ints(keysList)

	result := make([]interface{}, len(keysList))
	for i, key := range keysList {
		keyString := strconv.Itoa(key)
		if computed[keyString] {
			keyString = "~" + keyString
		}
		result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString))
	}

	return result
}

func expandMap(m map[string]string, prefix string) map[string]interface{} {
	// Submaps may not have a '%' key, so we can't count on this value being
	// here. If we don't have a count, just proceed as if we have have a map.
	if count, ok := m[prefix+"%"]; ok && count == "0" {
		return map[string]interface{}{}
	}

	result := make(map[string]interface{})
	for k := range m {
		if !strings.HasPrefix(k, prefix) {
			continue
		}

		key := k[len(prefix):]
		idx := strings.Index(key, ".")
		if idx != -1 {
			key = key[:idx]
		}
		if _, ok := result[key]; ok {
			continue
		}

		// skip the map count value
		if key == "%" {
			continue
		}

		result[key] = Expand(m, k[:len(prefix)+len(key)])
	}

	return result
}