github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/states/statefile/version2_upgrade.go (about)

     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  }