github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/terraform/state_upgrade_v2_to_v3.go (about) 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 }