github.com/gavinw2006/hashicorp-terraform@v0.11.12-beta1/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  }