github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/flatmap/expand.go (about)

     1  package flatmap
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/hil"
    10  )
    11  
    12  // Expand takes a map and a key (prefix) and expands that value into
    13  // a more complex structure. This is the reverse of the Flatten operation.
    14  func Expand(m map[string]string, key string) interface{} {
    15  	// If the key is exactly a key in the map, just return it
    16  	if v, ok := m[key]; ok {
    17  		if v == "true" {
    18  			return true
    19  		} else if v == "false" {
    20  			return false
    21  		}
    22  
    23  		return v
    24  	}
    25  
    26  	// Check if the key is an array, and if so, expand the array
    27  	if v, ok := m[key+".#"]; ok {
    28  		// If the count of the key is unknown, then just put the unknown
    29  		// value in the value itself. This will be detected by Terraform
    30  		// core later.
    31  		if v == hil.UnknownValue {
    32  			return v
    33  		}
    34  
    35  		return expandArray(m, key)
    36  	}
    37  
    38  	// Check if this is a prefix in the map
    39  	prefix := key + "."
    40  	for k := range m {
    41  		if strings.HasPrefix(k, prefix) {
    42  			return expandMap(m, prefix)
    43  		}
    44  	}
    45  
    46  	return nil
    47  }
    48  
    49  func expandArray(m map[string]string, prefix string) []interface{} {
    50  	num, err := strconv.ParseInt(m[prefix+".#"], 0, 0)
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  
    55  	// If the number of elements in this array is 0, then return an
    56  	// empty slice as there is nothing to expand. Trying to expand it
    57  	// anyway could lead to crashes as any child maps, arrays or sets
    58  	// that no longer exist are still shown as empty with a count of 0.
    59  	if num == 0 {
    60  		return []interface{}{}
    61  	}
    62  
    63  	// NOTE: "num" is not necessarily accurate, e.g. if a user tampers
    64  	// with state, so the following code should not crash when given a
    65  	// number of items more or less than what's given in num. The
    66  	// num key is mainly just a hint that this is a list or set.
    67  
    68  	// The Schema "Set" type stores its values in an array format, but
    69  	// using numeric hash values instead of ordinal keys. Take the set
    70  	// of keys regardless of value, and expand them in numeric order.
    71  	// See GH-11042 for more details.
    72  	keySet := map[int]bool{}
    73  	computed := map[string]bool{}
    74  	for k := range m {
    75  		if !strings.HasPrefix(k, prefix+".") {
    76  			continue
    77  		}
    78  
    79  		key := k[len(prefix)+1:]
    80  		idx := strings.Index(key, ".")
    81  		if idx != -1 {
    82  			key = key[:idx]
    83  		}
    84  
    85  		// skip the count value
    86  		if key == "#" {
    87  			continue
    88  		}
    89  
    90  		// strip the computed flag if there is one
    91  		if strings.HasPrefix(key, "~") {
    92  			key = key[1:]
    93  			computed[key] = true
    94  		}
    95  
    96  		k, err := strconv.Atoi(key)
    97  		if err != nil {
    98  			panic(err)
    99  		}
   100  		keySet[int(k)] = true
   101  	}
   102  
   103  	keysList := make([]int, 0, num)
   104  	for key := range keySet {
   105  		keysList = append(keysList, key)
   106  	}
   107  	sort.Ints(keysList)
   108  
   109  	result := make([]interface{}, len(keysList))
   110  	for i, key := range keysList {
   111  		keyString := strconv.Itoa(key)
   112  		if computed[keyString] {
   113  			keyString = "~" + keyString
   114  		}
   115  		result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString))
   116  	}
   117  
   118  	return result
   119  }
   120  
   121  func expandMap(m map[string]string, prefix string) map[string]interface{} {
   122  	// Submaps may not have a '%' key, so we can't count on this value being
   123  	// here. If we don't have a count, just proceed as if we have have a map.
   124  	if count, ok := m[prefix+"%"]; ok && count == "0" {
   125  		return map[string]interface{}{}
   126  	}
   127  
   128  	result := make(map[string]interface{})
   129  	for k := range m {
   130  		if !strings.HasPrefix(k, prefix) {
   131  			continue
   132  		}
   133  
   134  		key := k[len(prefix):]
   135  		idx := strings.Index(key, ".")
   136  		if idx != -1 {
   137  			key = key[:idx]
   138  		}
   139  		if _, ok := result[key]; ok {
   140  			continue
   141  		}
   142  
   143  		// skip the map count value
   144  		if key == "%" {
   145  			continue
   146  		}
   147  
   148  		result[key] = Expand(m, k[:len(prefix)+len(key)])
   149  	}
   150  
   151  	return result
   152  }