diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/states/statefile/version2_upgrade.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/states/statefile/version2_upgrade.go | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/states/statefile/version2_upgrade.go b/vendor/github.com/hashicorp/terraform/states/statefile/version2_upgrade.go new file mode 100644 index 0000000..2d03c07 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/states/statefile/version2_upgrade.go | |||
@@ -0,0 +1,145 @@ | |||
1 | package statefile | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "log" | ||
6 | "regexp" | ||
7 | "sort" | ||
8 | "strconv" | ||
9 | "strings" | ||
10 | |||
11 | "github.com/mitchellh/copystructure" | ||
12 | ) | ||
13 | |||
14 | func upgradeStateV2ToV3(old *stateV2) (*stateV3, error) { | ||
15 | if old == nil { | ||
16 | return (*stateV3)(nil), nil | ||
17 | } | ||
18 | |||
19 | var new *stateV3 | ||
20 | { | ||
21 | copy, err := copystructure.Config{Lock: true}.Copy(old) | ||
22 | if err != nil { | ||
23 | panic(err) | ||
24 | } | ||
25 | newWrongType := copy.(*stateV2) | ||
26 | newRightType := (stateV3)(*newWrongType) | ||
27 | new = &newRightType | ||
28 | } | ||
29 | |||
30 | // Set the new version number | ||
31 | new.Version = 3 | ||
32 | |||
33 | // Change the counts for things which look like maps to use the % | ||
34 | // syntax. Remove counts for empty collections - they will be added | ||
35 | // back in later. | ||
36 | for _, module := range new.Modules { | ||
37 | for _, resource := range module.Resources { | ||
38 | // Upgrade Primary | ||
39 | if resource.Primary != nil { | ||
40 | upgradeAttributesV2ToV3(resource.Primary) | ||
41 | } | ||
42 | |||
43 | // Upgrade Deposed | ||
44 | for _, deposed := range resource.Deposed { | ||
45 | upgradeAttributesV2ToV3(deposed) | ||
46 | } | ||
47 | } | ||
48 | } | ||
49 | |||
50 | return new, nil | ||
51 | } | ||
52 | |||
53 | func upgradeAttributesV2ToV3(instanceState *instanceStateV2) error { | ||
54 | collectionKeyRegexp := regexp.MustCompile(`^(.*\.)#$`) | ||
55 | collectionSubkeyRegexp := regexp.MustCompile(`^([^\.]+)\..*`) | ||
56 | |||
57 | // Identify the key prefix of anything which is a collection | ||
58 | var collectionKeyPrefixes []string | ||
59 | for key := range instanceState.Attributes { | ||
60 | if submatches := collectionKeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 { | ||
61 | collectionKeyPrefixes = append(collectionKeyPrefixes, submatches[0][1]) | ||
62 | } | ||
63 | } | ||
64 | sort.Strings(collectionKeyPrefixes) | ||
65 | |||
66 | log.Printf("[STATE UPGRADE] Detected the following collections in state: %v", collectionKeyPrefixes) | ||
67 | |||
68 | // This could be rolled into fewer loops, but it is somewhat clearer this way, and will not | ||
69 | // run very often. | ||
70 | for _, prefix := range collectionKeyPrefixes { | ||
71 | // First get the actual keys that belong to this prefix | ||
72 | var potentialKeysMatching []string | ||
73 | for key := range instanceState.Attributes { | ||
74 | if strings.HasPrefix(key, prefix) { | ||
75 | potentialKeysMatching = append(potentialKeysMatching, strings.TrimPrefix(key, prefix)) | ||
76 | } | ||
77 | } | ||
78 | sort.Strings(potentialKeysMatching) | ||
79 | |||
80 | var actualKeysMatching []string | ||
81 | for _, key := range potentialKeysMatching { | ||
82 | if submatches := collectionSubkeyRegexp.FindAllStringSubmatch(key, -1); len(submatches) > 0 { | ||
83 | actualKeysMatching = append(actualKeysMatching, submatches[0][1]) | ||
84 | } else { | ||
85 | if key != "#" { | ||
86 | actualKeysMatching = append(actualKeysMatching, key) | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | actualKeysMatching = uniqueSortedStrings(actualKeysMatching) | ||
91 | |||
92 | // Now inspect the keys in order to determine whether this is most likely to be | ||
93 | // a map, list or set. There is room for error here, so we log in each case. If | ||
94 | // there is no method of telling, we remove the key from the InstanceState in | ||
95 | // order that it will be recreated. Again, this could be rolled into fewer loops | ||
96 | // but we prefer clarity. | ||
97 | |||
98 | oldCountKey := fmt.Sprintf("%s#", prefix) | ||
99 | |||
100 | // First, detect "obvious" maps - which have non-numeric keys (mostly). | ||
101 | hasNonNumericKeys := false | ||
102 | for _, key := range actualKeysMatching { | ||
103 | if _, err := strconv.Atoi(key); err != nil { | ||
104 | hasNonNumericKeys = true | ||
105 | } | ||
106 | } | ||
107 | if hasNonNumericKeys { | ||
108 | newCountKey := fmt.Sprintf("%s%%", prefix) | ||
109 | |||
110 | instanceState.Attributes[newCountKey] = instanceState.Attributes[oldCountKey] | ||
111 | delete(instanceState.Attributes, oldCountKey) | ||
112 | log.Printf("[STATE UPGRADE] Detected %s as a map. Replaced count = %s", | ||
113 | strings.TrimSuffix(prefix, "."), instanceState.Attributes[newCountKey]) | ||
114 | } | ||
115 | |||
116 | // Now detect empty collections and remove them from state. | ||
117 | if len(actualKeysMatching) == 0 { | ||
118 | delete(instanceState.Attributes, oldCountKey) | ||
119 | log.Printf("[STATE UPGRADE] Detected %s as an empty collection. Removed from state.", | ||
120 | strings.TrimSuffix(prefix, ".")) | ||
121 | } | ||
122 | } | ||
123 | |||
124 | return nil | ||
125 | } | ||
126 | |||
127 | // uniqueSortedStrings removes duplicates from a slice of strings and returns | ||
128 | // a sorted slice of the unique strings. | ||
129 | func uniqueSortedStrings(input []string) []string { | ||
130 | uniquemap := make(map[string]struct{}) | ||
131 | for _, str := range input { | ||
132 | uniquemap[str] = struct{}{} | ||
133 | } | ||
134 | |||
135 | output := make([]string, len(uniquemap)) | ||
136 | |||
137 | i := 0 | ||
138 | for key := range uniquemap { | ||
139 | output[i] = key | ||
140 | i = i + 1 | ||
141 | } | ||
142 | |||
143 | sort.Strings(output) | ||
144 | return output | ||
145 | } | ||