github.com/GoogleCloudPlatform/terraformer@v0.8.18/terraformutils/flatmap.go (about)

     1  // Copyright 2018 The Terraformer Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package terraformutils
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"github.com/hashicorp/terraform/configs/hcl2shim"
    25  	"github.com/zclconf/go-cty/cty"
    26  )
    27  
    28  type Flatmapper interface {
    29  	Parse(ty cty.Type) (map[string]interface{}, error)
    30  }
    31  
    32  type FlatmapParser struct {
    33  	Flatmapper
    34  	attributes       map[string]string
    35  	ignoreKeys       []*regexp.Regexp
    36  	allowEmptyValues []*regexp.Regexp
    37  }
    38  
    39  func NewFlatmapParser(attributes map[string]string, ignoreKeys []*regexp.Regexp, allowEmptyValues []*regexp.Regexp) *FlatmapParser {
    40  	return &FlatmapParser{
    41  		attributes:       attributes,
    42  		ignoreKeys:       ignoreKeys,
    43  		allowEmptyValues: allowEmptyValues,
    44  	}
    45  }
    46  
    47  // FromFlatmap converts a map compatible with what would be produced
    48  // by the "flatmap" package to a map[string]interface{} object type.
    49  //
    50  // The intended result type must be provided in order to guide how the
    51  // map contents are decoded. This must be an object type or this function
    52  // will panic.
    53  //
    54  // Flatmap values can only represent maps when they are of primitive types,
    55  // so the given type must not have any maps of complex types or the result
    56  // is undefined.
    57  //
    58  // The result may contain null values if the given map does not contain keys
    59  // for all of the different key paths implied by the given type.
    60  func (p *FlatmapParser) Parse(ty cty.Type) (map[string]interface{}, error) {
    61  	if p.attributes == nil {
    62  		return nil, nil
    63  	}
    64  	if !ty.IsObjectType() {
    65  		return nil, fmt.Errorf("FlatmapParser#Parse called on %#v", ty)
    66  	}
    67  	return p.fromFlatmapObject("", ty.AttributeTypes())
    68  }
    69  
    70  func (p *FlatmapParser) fromFlatmapValue(key string, ty cty.Type) (interface{}, error) {
    71  	switch {
    72  	case ty.IsPrimitiveType():
    73  		return p.fromFlatmapPrimitive(key)
    74  	case ty.IsObjectType():
    75  		return p.fromFlatmapObject(key+".", ty.AttributeTypes())
    76  	case ty.IsTupleType():
    77  		return p.fromFlatmapTuple(key+".", ty.TupleElementTypes())
    78  	case ty.IsMapType():
    79  		return p.fromFlatmapMap(key+".", ty.ElementType())
    80  	case ty.IsListType():
    81  		return p.fromFlatmapList(key+".", ty.ElementType())
    82  	case ty.IsSetType():
    83  		return p.fromFlatmapSet(key+".", ty.ElementType())
    84  	default:
    85  		return nil, fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName())
    86  	}
    87  }
    88  
    89  func (p *FlatmapParser) fromFlatmapPrimitive(key string) (interface{}, error) {
    90  	value, ok := p.attributes[key]
    91  	if !ok {
    92  		return nil, nil
    93  	}
    94  	return value, nil
    95  }
    96  
    97  func (p *FlatmapParser) fromFlatmapObject(prefix string, tys map[string]cty.Type) (map[string]interface{}, error) {
    98  	values := make(map[string]interface{})
    99  	for name, ty := range tys {
   100  		inAttributes := false
   101  		attributeName := ""
   102  		for k := range p.attributes {
   103  			if k == prefix+name {
   104  				attributeName = k
   105  				inAttributes = true
   106  				break
   107  			}
   108  			if k == name {
   109  				attributeName = k
   110  				inAttributes = true
   111  				break
   112  			}
   113  
   114  			if strings.HasPrefix(k, prefix+name+".") {
   115  				attributeName = k
   116  				inAttributes = true
   117  				break
   118  			}
   119  			lastAttribute := (prefix + name)[len(prefix):]
   120  			if lastAttribute == k {
   121  				attributeName = k
   122  				inAttributes = true
   123  				break
   124  			}
   125  		}
   126  
   127  		if _, exist := p.attributes[prefix+name+".#"]; exist {
   128  			attributeName = prefix + name + ".#"
   129  			inAttributes = true
   130  		}
   131  
   132  		if _, exist := p.attributes[prefix+name+".%"]; exist {
   133  			attributeName = prefix + name + ".%"
   134  			inAttributes = true
   135  		}
   136  
   137  		if !inAttributes {
   138  			continue
   139  		}
   140  		if p.isAttributeIgnored(prefix + name) {
   141  			continue
   142  		}
   143  		value, err := p.fromFlatmapValue(prefix+name, ty)
   144  		if err != nil {
   145  			return nil, err
   146  		}
   147  		if p.isValueAllowed(value, attributeName) {
   148  			values[name] = value
   149  		}
   150  	}
   151  	if len(values) == 0 {
   152  		return nil, nil
   153  	}
   154  	return values, nil
   155  }
   156  
   157  func (p *FlatmapParser) fromFlatmapTuple(prefix string, tys []cty.Type) ([]interface{}, error) {
   158  	// if the container is unknown, there is no count string
   159  	listName := strings.TrimRight(prefix, ".")
   160  	if p.attributes[listName] == hcl2shim.UnknownVariableValue {
   161  		return nil, nil
   162  	}
   163  
   164  	countStr, exists := p.attributes[prefix+"#"]
   165  	if !exists {
   166  		return nil, nil
   167  	}
   168  	if countStr == hcl2shim.UnknownVariableValue {
   169  		return nil, nil
   170  	}
   171  
   172  	count, err := strconv.Atoi(countStr)
   173  	if err != nil {
   174  		return nil, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
   175  	}
   176  	if count != len(tys) {
   177  		return nil, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(tys))
   178  	}
   179  
   180  	var values []interface{}
   181  	for i, ty := range tys {
   182  		key := prefix + strconv.Itoa(i)
   183  		value, err := p.fromFlatmapValue(key, ty)
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  		if p.isValueAllowed(value, prefix) {
   188  			values = append(values, value)
   189  		}
   190  	}
   191  	if len(values) == 0 {
   192  		return nil, nil
   193  	}
   194  	return values, nil
   195  }
   196  
   197  func (p *FlatmapParser) fromFlatmapMap(prefix string, ty cty.Type) (map[string]interface{}, error) {
   198  	// if the container is unknown, there is no count string
   199  	listName := strings.TrimRight(prefix, ".")
   200  	if p.attributes[listName] == hcl2shim.UnknownVariableValue {
   201  		return nil, nil
   202  	}
   203  
   204  	// We actually don't really care about the "count" of a map for our
   205  	// purposes here, but we do need to check if it _exists_ in order to
   206  	// recognize the difference between null (not set at all) and empty.
   207  	strCount, exists := p.attributes[prefix+"%"]
   208  	if !exists {
   209  		return nil, nil
   210  	}
   211  	if strCount == hcl2shim.UnknownVariableValue {
   212  		return nil, nil
   213  	}
   214  
   215  	values := make(map[string]interface{})
   216  	for fullKey := range p.attributes {
   217  		if !strings.HasPrefix(fullKey, prefix) {
   218  			continue
   219  		}
   220  
   221  		// The flatmap format doesn't allow us to distinguish between keys
   222  		// that contain periods and nested objects, so by convention a
   223  		// map is only ever of primitive type in flatmap, and we just assume
   224  		// that the remainder of the raw key (dots and all) is the key we
   225  		// want in the result value.
   226  		key := fullKey[len(prefix):]
   227  		if key == "%" {
   228  			// Ignore the "count" key
   229  			continue
   230  		}
   231  		if p.isAttributeIgnored(fullKey) {
   232  			continue
   233  		}
   234  		value, err := p.fromFlatmapValue(fullKey, ty)
   235  		if err != nil {
   236  			return nil, err
   237  		}
   238  		if p.isValueAllowed(value, prefix) {
   239  			values[key] = value
   240  		}
   241  	}
   242  	if len(values) == 0 {
   243  		return nil, nil
   244  	}
   245  	return values, nil
   246  }
   247  
   248  func (p *FlatmapParser) fromFlatmapList(prefix string, ty cty.Type) ([]interface{}, error) {
   249  	// if the container is unknown, there is no count string
   250  	listName := strings.TrimRight(prefix, ".")
   251  	if p.attributes[listName] == hcl2shim.UnknownVariableValue {
   252  		return nil, nil
   253  	}
   254  
   255  	countStr, exists := p.attributes[prefix+"#"]
   256  	if !exists {
   257  		return nil, nil
   258  	}
   259  	if countStr == hcl2shim.UnknownVariableValue {
   260  		return nil, nil
   261  	}
   262  
   263  	count, err := strconv.Atoi(countStr)
   264  	if err != nil {
   265  		return nil, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
   266  	}
   267  
   268  	if count == 0 {
   269  		return nil, nil
   270  	}
   271  
   272  	var values []interface{}
   273  	for i := 0; i < count; i++ {
   274  		key := prefix + strconv.Itoa(i)
   275  
   276  		if p.isAttributeIgnored(key) {
   277  			continue
   278  		}
   279  
   280  		value, err := p.fromFlatmapValue(key, ty)
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  		if p.isValueAllowed(value, prefix) {
   285  			values = append(values, value)
   286  		}
   287  	}
   288  	return values, nil
   289  }
   290  
   291  func (p *FlatmapParser) fromFlatmapSet(prefix string, ty cty.Type) ([]interface{}, error) {
   292  	// if the container is unknown, there is no count string
   293  	listName := strings.TrimRight(prefix, ".")
   294  	if p.attributes[listName] == hcl2shim.UnknownVariableValue {
   295  		return nil, nil
   296  	}
   297  
   298  	strCount, exists := p.attributes[prefix+"#"]
   299  	if !exists {
   300  		return nil, nil
   301  	}
   302  	if strCount == hcl2shim.UnknownVariableValue {
   303  		return nil, nil
   304  	}
   305  
   306  	// Keep track of keys we've seen, se we don't add the same set value
   307  	// multiple times. The cty.Set will normally de-duplicate values, but we may
   308  	// have unknown values that would not show as equivalent.
   309  	seen := map[string]bool{}
   310  
   311  	var values []interface{}
   312  	for fullKey := range p.attributes {
   313  		if !strings.HasPrefix(fullKey, prefix) {
   314  			continue
   315  		}
   316  
   317  		subKey := fullKey[len(prefix):]
   318  		if subKey == "#" {
   319  			// Ignore the "count" key
   320  			continue
   321  		}
   322  
   323  		key := fullKey
   324  
   325  		if p.isAttributeIgnored(fullKey) {
   326  			continue
   327  		}
   328  
   329  		if dot := strings.IndexByte(subKey, '.'); dot != -1 {
   330  			key = fullKey[:dot+len(prefix)]
   331  		}
   332  
   333  		if seen[key] {
   334  			continue
   335  		}
   336  		seen[key] = true
   337  
   338  		// The flatmap format doesn't allow us to distinguish between keys
   339  		// that contain periods and nested objects, so by convention a
   340  		// map is only ever of primitive type in flatmap, and we just assume
   341  		// that the remainder of the raw key (dots and all) is the key we
   342  		// want in the result value.
   343  
   344  		value, err := p.fromFlatmapValue(key, ty)
   345  		if err != nil {
   346  			return nil, err
   347  		}
   348  		if p.isValueAllowed(value, prefix) {
   349  			values = append(values, value)
   350  		}
   351  	}
   352  	if len(values) == 0 {
   353  		return nil, nil
   354  	}
   355  	return values, nil
   356  }
   357  
   358  func (p *FlatmapParser) isAttributeIgnored(name string) bool {
   359  	ignored := false
   360  	for _, pattern := range p.ignoreKeys {
   361  		if pattern.MatchString(name) {
   362  			ignored = true
   363  			break
   364  		}
   365  	}
   366  	return ignored
   367  }
   368  
   369  func (p *FlatmapParser) isValueAllowed(value interface{}, prefix string) bool {
   370  	if !reflect.ValueOf(value).IsValid() {
   371  		return false
   372  	}
   373  	switch reflect.ValueOf(value).Kind() {
   374  	case reflect.Slice:
   375  		if reflect.ValueOf(value).Len() == 0 {
   376  			return false
   377  		}
   378  
   379  		for i := 0; i < reflect.ValueOf(value).Len(); i++ {
   380  			if !reflect.ValueOf(value).Index(i).IsZero() {
   381  				return true
   382  			}
   383  		}
   384  	case reflect.Map:
   385  		if reflect.ValueOf(value).Len() == 0 {
   386  			return false
   387  		}
   388  	}
   389  	if !reflect.ValueOf(value).IsZero() {
   390  		return true
   391  	}
   392  
   393  	allowed := false
   394  	for _, pattern := range p.allowEmptyValues {
   395  		if pattern.MatchString(prefix) {
   396  			allowed = true
   397  			break
   398  		}
   399  	}
   400  	return allowed
   401  }