github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/configs/hcl2shim/flatmap.go (about)

     1  package hcl2shim
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"github.com/zclconf/go-cty/cty/convert"
     9  
    10  	"github.com/zclconf/go-cty/cty"
    11  )
    12  
    13  // FlatmapValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic
    14  // types library that HCL2 uses) to a map compatible with what would be
    15  // produced by the "flatmap" package.
    16  //
    17  // The type of the given value informs the structure of the resulting map.
    18  // The value must be of an object type or this function will panic.
    19  //
    20  // Flatmap values can only represent maps when they are of primitive types,
    21  // so the given value must not have any maps of complex types or the result
    22  // is undefined.
    23  func FlatmapValueFromHCL2(v cty.Value) map[string]string {
    24  	if v.IsNull() {
    25  		return nil
    26  	}
    27  
    28  	if !v.Type().IsObjectType() {
    29  		panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", v.Type()))
    30  	}
    31  
    32  	m := make(map[string]string)
    33  	flatmapValueFromHCL2Map(m, "", v)
    34  	return m
    35  }
    36  
    37  func flatmapValueFromHCL2Value(m map[string]string, key string, val cty.Value) {
    38  	ty := val.Type()
    39  	switch {
    40  	case ty.IsPrimitiveType() || ty == cty.DynamicPseudoType:
    41  		flatmapValueFromHCL2Primitive(m, key, val)
    42  	case ty.IsObjectType() || ty.IsMapType():
    43  		flatmapValueFromHCL2Map(m, key+".", val)
    44  	case ty.IsTupleType() || ty.IsListType() || ty.IsSetType():
    45  		flatmapValueFromHCL2Seq(m, key+".", val)
    46  	default:
    47  		panic(fmt.Sprintf("cannot encode %s to flatmap", ty.FriendlyName()))
    48  	}
    49  }
    50  
    51  func flatmapValueFromHCL2Primitive(m map[string]string, key string, val cty.Value) {
    52  	if !val.IsKnown() {
    53  		m[key] = UnknownVariableValue
    54  		return
    55  	}
    56  	if val.IsNull() {
    57  		// Omit entirely
    58  		return
    59  	}
    60  
    61  	var err error
    62  	val, err = convert.Convert(val, cty.String)
    63  	if err != nil {
    64  		// Should not be possible, since all primitive types can convert to string.
    65  		panic(fmt.Sprintf("invalid primitive encoding to flatmap: %s", err))
    66  	}
    67  	m[key] = val.AsString()
    68  }
    69  
    70  func flatmapValueFromHCL2Map(m map[string]string, prefix string, val cty.Value) {
    71  	if val.IsNull() {
    72  		// Omit entirely
    73  		return
    74  	}
    75  	if !val.IsKnown() {
    76  		switch {
    77  		case val.Type().IsObjectType():
    78  			// Whole objects can't be unknown in flatmap, so instead we'll
    79  			// just write all of the attribute values out as unknown.
    80  			for name, aty := range val.Type().AttributeTypes() {
    81  				flatmapValueFromHCL2Value(m, prefix+name, cty.UnknownVal(aty))
    82  			}
    83  		default:
    84  			m[prefix+"%"] = UnknownVariableValue
    85  		}
    86  		return
    87  	}
    88  
    89  	len := 0
    90  	for it := val.ElementIterator(); it.Next(); {
    91  		ak, av := it.Element()
    92  		name := ak.AsString()
    93  		flatmapValueFromHCL2Value(m, prefix+name, av)
    94  		len++
    95  	}
    96  	if !val.Type().IsObjectType() { // objects don't have an explicit count included, since their attribute count is fixed
    97  		m[prefix+"%"] = strconv.Itoa(len)
    98  	}
    99  }
   100  
   101  func flatmapValueFromHCL2Seq(m map[string]string, prefix string, val cty.Value) {
   102  	if val.IsNull() {
   103  		// Omit entirely
   104  		return
   105  	}
   106  	if !val.IsKnown() {
   107  		m[prefix+"#"] = UnknownVariableValue
   108  		return
   109  	}
   110  
   111  	// For sets this won't actually generate exactly what helper/schema would've
   112  	// generated, because we don't have access to the set key function it
   113  	// would've used. However, in practice it doesn't actually matter what the
   114  	// keys are as long as they are unique, so we'll just generate sequential
   115  	// indexes for them as if it were a list.
   116  	//
   117  	// An important implication of this, however, is that the set ordering will
   118  	// not be consistent across mutations and so different keys may be assigned
   119  	// to the same value when round-tripping. Since this shim is intended to
   120  	// be short-lived and not used for round-tripping, we accept this.
   121  	i := 0
   122  	for it := val.ElementIterator(); it.Next(); {
   123  		_, av := it.Element()
   124  		key := prefix + strconv.Itoa(i)
   125  		flatmapValueFromHCL2Value(m, key, av)
   126  		i++
   127  	}
   128  	m[prefix+"#"] = strconv.Itoa(i)
   129  }
   130  
   131  // HCL2ValueFromFlatmap converts a map compatible with what would be produced
   132  // by the "flatmap" package to a HCL2 (really, the cty dynamic types library
   133  // that HCL2 uses) object type.
   134  //
   135  // The intended result type must be provided in order to guide how the
   136  // map contents are decoded. This must be an object type or this function
   137  // will panic.
   138  //
   139  // Flatmap values can only represent maps when they are of primitive types,
   140  // so the given type must not have any maps of complex types or the result
   141  // is undefined.
   142  //
   143  // The result may contain null values if the given map does not contain keys
   144  // for all of the different key paths implied by the given type.
   145  func HCL2ValueFromFlatmap(m map[string]string, ty cty.Type) (cty.Value, error) {
   146  	if m == nil {
   147  		return cty.NullVal(ty), nil
   148  	}
   149  	if !ty.IsObjectType() {
   150  		panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", ty))
   151  	}
   152  
   153  	return hcl2ValueFromFlatmapObject(m, "", ty.AttributeTypes())
   154  }
   155  
   156  func hcl2ValueFromFlatmapValue(m map[string]string, key string, ty cty.Type) (cty.Value, error) {
   157  	var val cty.Value
   158  	var err error
   159  	switch {
   160  	case ty.IsPrimitiveType():
   161  		val, err = hcl2ValueFromFlatmapPrimitive(m, key, ty)
   162  	case ty.IsObjectType():
   163  		val, err = hcl2ValueFromFlatmapObject(m, key+".", ty.AttributeTypes())
   164  	case ty.IsTupleType():
   165  		val, err = hcl2ValueFromFlatmapTuple(m, key+".", ty.TupleElementTypes())
   166  	case ty.IsMapType():
   167  		val, err = hcl2ValueFromFlatmapMap(m, key+".", ty)
   168  	case ty.IsListType():
   169  		val, err = hcl2ValueFromFlatmapList(m, key+".", ty)
   170  	case ty.IsSetType():
   171  		val, err = hcl2ValueFromFlatmapSet(m, key+".", ty)
   172  	default:
   173  		err = fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName())
   174  	}
   175  
   176  	if err != nil {
   177  		return cty.DynamicVal, err
   178  	}
   179  	return val, nil
   180  }
   181  
   182  func hcl2ValueFromFlatmapPrimitive(m map[string]string, key string, ty cty.Type) (cty.Value, error) {
   183  	rawVal, exists := m[key]
   184  	if !exists {
   185  		return cty.NullVal(ty), nil
   186  	}
   187  	if rawVal == UnknownVariableValue {
   188  		return cty.UnknownVal(ty), nil
   189  	}
   190  
   191  	var err error
   192  	val := cty.StringVal(rawVal)
   193  	val, err = convert.Convert(val, ty)
   194  	if err != nil {
   195  		// This should never happen for _valid_ input, but flatmap data might
   196  		// be tampered with by the user and become invalid.
   197  		return cty.DynamicVal, fmt.Errorf("invalid value for %q in state: %s", key, err)
   198  	}
   199  
   200  	return val, nil
   201  }
   202  
   203  func hcl2ValueFromFlatmapObject(m map[string]string, prefix string, atys map[string]cty.Type) (cty.Value, error) {
   204  	vals := make(map[string]cty.Value)
   205  	for name, aty := range atys {
   206  		val, err := hcl2ValueFromFlatmapValue(m, prefix+name, aty)
   207  		if err != nil {
   208  			return cty.DynamicVal, err
   209  		}
   210  		vals[name] = val
   211  	}
   212  	return cty.ObjectVal(vals), nil
   213  }
   214  
   215  func hcl2ValueFromFlatmapTuple(m map[string]string, prefix string, etys []cty.Type) (cty.Value, error) {
   216  	var vals []cty.Value
   217  
   218  	// if the container is unknown, there is no count string
   219  	listName := strings.TrimRight(prefix, ".")
   220  	if m[listName] == UnknownVariableValue {
   221  		return cty.UnknownVal(cty.Tuple(etys)), nil
   222  	}
   223  
   224  	countStr, exists := m[prefix+"#"]
   225  	if !exists {
   226  		return cty.NullVal(cty.Tuple(etys)), nil
   227  	}
   228  	if countStr == UnknownVariableValue {
   229  		return cty.UnknownVal(cty.Tuple(etys)), nil
   230  	}
   231  
   232  	count, err := strconv.Atoi(countStr)
   233  	if err != nil {
   234  		return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
   235  	}
   236  	if count != len(etys) {
   237  		return cty.DynamicVal, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(etys))
   238  	}
   239  
   240  	vals = make([]cty.Value, len(etys))
   241  	for i, ety := range etys {
   242  		key := prefix + strconv.Itoa(i)
   243  		val, err := hcl2ValueFromFlatmapValue(m, key, ety)
   244  		if err != nil {
   245  			return cty.DynamicVal, err
   246  		}
   247  		vals[i] = val
   248  	}
   249  	return cty.TupleVal(vals), nil
   250  }
   251  
   252  func hcl2ValueFromFlatmapMap(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
   253  	vals := make(map[string]cty.Value)
   254  	ety := ty.ElementType()
   255  
   256  	// if the container is unknown, there is no count string
   257  	listName := strings.TrimRight(prefix, ".")
   258  	if m[listName] == UnknownVariableValue {
   259  		return cty.UnknownVal(ty), nil
   260  	}
   261  
   262  	// We actually don't really care about the "count" of a map for our
   263  	// purposes here, but we do need to check if it _exists_ in order to
   264  	// recognize the difference between null (not set at all) and empty.
   265  	if strCount, exists := m[prefix+"%"]; !exists {
   266  		return cty.NullVal(ty), nil
   267  	} else if strCount == UnknownVariableValue {
   268  		return cty.UnknownVal(ty), nil
   269  	}
   270  
   271  	for fullKey := range m {
   272  		if !strings.HasPrefix(fullKey, prefix) {
   273  			continue
   274  		}
   275  
   276  		// The flatmap format doesn't allow us to distinguish between keys
   277  		// that contain periods and nested objects, so by convention a
   278  		// map is only ever of primitive type in flatmap, and we just assume
   279  		// that the remainder of the raw key (dots and all) is the key we
   280  		// want in the result value.
   281  		key := fullKey[len(prefix):]
   282  		if key == "%" {
   283  			// Ignore the "count" key
   284  			continue
   285  		}
   286  
   287  		val, err := hcl2ValueFromFlatmapValue(m, fullKey, ety)
   288  		if err != nil {
   289  			return cty.DynamicVal, err
   290  		}
   291  		vals[key] = val
   292  	}
   293  
   294  	if len(vals) == 0 {
   295  		return cty.MapValEmpty(ety), nil
   296  	}
   297  	return cty.MapVal(vals), nil
   298  }
   299  
   300  func hcl2ValueFromFlatmapList(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
   301  	var vals []cty.Value
   302  
   303  	// if the container is unknown, there is no count string
   304  	listName := strings.TrimRight(prefix, ".")
   305  	if m[listName] == UnknownVariableValue {
   306  		return cty.UnknownVal(ty), nil
   307  	}
   308  
   309  	countStr, exists := m[prefix+"#"]
   310  	if !exists {
   311  		return cty.NullVal(ty), nil
   312  	}
   313  	if countStr == UnknownVariableValue {
   314  		return cty.UnknownVal(ty), nil
   315  	}
   316  
   317  	count, err := strconv.Atoi(countStr)
   318  	if err != nil {
   319  		return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
   320  	}
   321  
   322  	ety := ty.ElementType()
   323  	if count == 0 {
   324  		return cty.ListValEmpty(ety), nil
   325  	}
   326  
   327  	vals = make([]cty.Value, count)
   328  	for i := 0; i < count; i++ {
   329  		key := prefix + strconv.Itoa(i)
   330  		val, err := hcl2ValueFromFlatmapValue(m, key, ety)
   331  		if err != nil {
   332  			return cty.DynamicVal, err
   333  		}
   334  		vals[i] = val
   335  	}
   336  
   337  	return cty.ListVal(vals), nil
   338  }
   339  
   340  func hcl2ValueFromFlatmapSet(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
   341  	var vals []cty.Value
   342  	ety := ty.ElementType()
   343  
   344  	// if the container is unknown, there is no count string
   345  	listName := strings.TrimRight(prefix, ".")
   346  	if m[listName] == UnknownVariableValue {
   347  		return cty.UnknownVal(ty), nil
   348  	}
   349  
   350  	strCount, exists := m[prefix+"#"]
   351  	if !exists {
   352  		return cty.NullVal(ty), nil
   353  	} else if strCount == UnknownVariableValue {
   354  		return cty.UnknownVal(ty), nil
   355  	}
   356  
   357  	// Keep track of keys we've seen, se we don't add the same set value
   358  	// multiple times. The cty.Set will normally de-duplicate values, but we may
   359  	// have unknown values that would not show as equivalent.
   360  	seen := map[string]bool{}
   361  
   362  	for fullKey := range m {
   363  		if !strings.HasPrefix(fullKey, prefix) {
   364  			continue
   365  		}
   366  		subKey := fullKey[len(prefix):]
   367  		if subKey == "#" {
   368  			// Ignore the "count" key
   369  			continue
   370  		}
   371  		key := fullKey
   372  		if dot := strings.IndexByte(subKey, '.'); dot != -1 {
   373  			key = fullKey[:dot+len(prefix)]
   374  		}
   375  
   376  		if seen[key] {
   377  			continue
   378  		}
   379  
   380  		seen[key] = true
   381  
   382  		// The flatmap format doesn't allow us to distinguish between keys
   383  		// that contain periods and nested objects, so by convention a
   384  		// map is only ever of primitive type in flatmap, and we just assume
   385  		// that the remainder of the raw key (dots and all) is the key we
   386  		// want in the result value.
   387  
   388  		val, err := hcl2ValueFromFlatmapValue(m, key, ety)
   389  		if err != nil {
   390  			return cty.DynamicVal, err
   391  		}
   392  		vals = append(vals, val)
   393  	}
   394  
   395  	if len(vals) == 0 && strCount == "1" {
   396  		// An empty set wouldn't be represented in the flatmap, so this must be
   397  		// a single empty object since the count is actually 1.
   398  		// Add an appropriately typed null value to the set.
   399  		var val cty.Value
   400  		switch {
   401  		case ety.IsMapType():
   402  			val = cty.MapValEmpty(ety)
   403  		case ety.IsListType():
   404  			val = cty.ListValEmpty(ety)
   405  		case ety.IsSetType():
   406  			val = cty.SetValEmpty(ety)
   407  		case ety.IsObjectType():
   408  			// TODO: cty.ObjectValEmpty
   409  			objectMap := map[string]cty.Value{}
   410  			for attr, ty := range ety.AttributeTypes() {
   411  				objectMap[attr] = cty.NullVal(ty)
   412  			}
   413  			val = cty.ObjectVal(objectMap)
   414  		default:
   415  			val = cty.NullVal(ety)
   416  		}
   417  		vals = append(vals, val)
   418  
   419  	} else if len(vals) == 0 {
   420  		return cty.SetValEmpty(ety), nil
   421  	}
   422  
   423  	return cty.SetVal(vals), nil
   424  }