github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/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  	// The Schema "Set" type stores its values in an array format, but
    64  	// using numeric hash values instead of ordinal keys. Take the set
    65  	// of keys regardless of value, and expand them in numeric order.
    66  	// See GH-11042 for more details.
    67  	keySet := map[int]bool{}
    68  	computed := map[string]bool{}
    69  	for k := range m {
    70  		if !strings.HasPrefix(k, prefix+".") {
    71  			continue
    72  		}
    73  
    74  		key := k[len(prefix)+1:]
    75  		idx := strings.Index(key, ".")
    76  		if idx != -1 {
    77  			key = key[:idx]
    78  		}
    79  
    80  		// skip the count value
    81  		if key == "#" {
    82  			continue
    83  		}
    84  
    85  		// strip the computed flag if there is one
    86  		if strings.HasPrefix(key, "~") {
    87  			key = key[1:]
    88  			computed[key] = true
    89  		}
    90  
    91  		k, err := strconv.Atoi(key)
    92  		if err != nil {
    93  			panic(err)
    94  		}
    95  		keySet[int(k)] = true
    96  	}
    97  
    98  	keysList := make([]int, 0, num)
    99  	for key := range keySet {
   100  		keysList = append(keysList, key)
   101  	}
   102  	sort.Ints(keysList)
   103  
   104  	result := make([]interface{}, num)
   105  	for i, key := range keysList {
   106  		keyString := strconv.Itoa(key)
   107  		if computed[keyString] {
   108  			keyString = "~" + keyString
   109  		}
   110  		result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString))
   111  	}
   112  
   113  	return result
   114  }
   115  
   116  func expandMap(m map[string]string, prefix string) map[string]interface{} {
   117  	// Submaps may not have a '%' key, so we can't count on this value being
   118  	// here. If we don't have a count, just proceed as if we have have a map.
   119  	if count, ok := m[prefix+"%"]; ok && count == "0" {
   120  		return map[string]interface{}{}
   121  	}
   122  
   123  	result := make(map[string]interface{})
   124  	for k := range m {
   125  		if !strings.HasPrefix(k, prefix) {
   126  			continue
   127  		}
   128  
   129  		key := k[len(prefix):]
   130  		idx := strings.Index(key, ".")
   131  		if idx != -1 {
   132  			key = key[:idx]
   133  		}
   134  		if _, ok := result[key]; ok {
   135  			continue
   136  		}
   137  
   138  		// skip the map count value
   139  		if key == "%" {
   140  			continue
   141  		}
   142  
   143  		result[key] = Expand(m, k[:len(prefix)+len(key)])
   144  	}
   145  
   146  	return result
   147  }