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