github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/hcl2shim/paths.go (about)

     1  package hcl2shim
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/zclconf/go-cty/cty"
    10  )
    11  
    12  // RequiresReplace takes a list of flatmapped paths from a
    13  // InstanceDiff.Attributes along with the corresponding cty.Type, and returns
    14  // the list of the cty.Paths that are flagged as causing the resource
    15  // replacement (RequiresNew).
    16  // This will filter out redundant paths, paths that refer to flatmapped indexes
    17  // (e.g. "#", "%"), and will return any changes within a set as the path to the
    18  // set itself.
    19  func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) {
    20  	var paths []cty.Path
    21  
    22  	for _, attr := range attrs {
    23  		p, err := requiresReplacePath(attr, ty)
    24  		if err != nil {
    25  			return nil, err
    26  		}
    27  
    28  		paths = append(paths, p)
    29  	}
    30  
    31  	// now trim off any trailing paths that aren't GetAttrSteps, since only an
    32  	// attribute itself can require replacement
    33  	paths = trimPaths(paths)
    34  
    35  	// There may be redundant paths due to set elements or index attributes
    36  	// Do some ugly n^2 filtering, but these are always fairly small sets.
    37  	for i := 0; i < len(paths)-1; i++ {
    38  		for j := i + 1; j < len(paths); j++ {
    39  			if reflect.DeepEqual(paths[i], paths[j]) {
    40  				// swap the tail and slice it off
    41  				paths[j], paths[len(paths)-1] = paths[len(paths)-1], paths[j]
    42  				paths = paths[:len(paths)-1]
    43  				j--
    44  			}
    45  		}
    46  	}
    47  
    48  	return paths, nil
    49  }
    50  
    51  // trimPaths removes any trailing steps that aren't of type GetAttrSet, since
    52  // only an attribute itself can require replacement
    53  func trimPaths(paths []cty.Path) []cty.Path {
    54  	var trimmed []cty.Path
    55  	for _, path := range paths {
    56  		path = trimPath(path)
    57  		if len(path) > 0 {
    58  			trimmed = append(trimmed, path)
    59  		}
    60  	}
    61  	return trimmed
    62  }
    63  
    64  func trimPath(path cty.Path) cty.Path {
    65  	for len(path) > 0 {
    66  		_, isGetAttr := path[len(path)-1].(cty.GetAttrStep)
    67  		if isGetAttr {
    68  			break
    69  		}
    70  		path = path[:len(path)-1]
    71  	}
    72  	return path
    73  }
    74  
    75  // requiresReplacePath takes a key from a flatmap along with the cty.Type
    76  // describing the structure, and returns the cty.Path that would be used to
    77  // reference the nested value in the data structure.
    78  // This is used specifically to record the RequiresReplace attributes from a
    79  // ResourceInstanceDiff.
    80  func requiresReplacePath(k string, ty cty.Type) (cty.Path, error) {
    81  	if k == "" {
    82  		return nil, nil
    83  	}
    84  	if !ty.IsObjectType() {
    85  		panic(fmt.Sprintf("requires replace path on non-object type: %#v", ty))
    86  	}
    87  
    88  	path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes())
    89  	if err != nil {
    90  		return path, fmt.Errorf("[%s] %s", k, err)
    91  	}
    92  	return path, nil
    93  }
    94  
    95  func pathSplit(p string) (string, string) {
    96  	parts := strings.SplitN(p, ".", 2)
    97  	head := parts[0]
    98  	rest := ""
    99  	if len(parts) > 1 {
   100  		rest = parts[1]
   101  	}
   102  	return head, rest
   103  }
   104  
   105  func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) {
   106  	k, rest := pathSplit(key)
   107  
   108  	path := cty.Path{cty.GetAttrStep{Name: k}}
   109  
   110  	ty, ok := atys[k]
   111  	if !ok {
   112  		return path, fmt.Errorf("attribute %q not found", k)
   113  	}
   114  
   115  	if rest == "" {
   116  		return path, nil
   117  	}
   118  
   119  	p, err := pathFromFlatmapKeyValue(rest, ty)
   120  	if err != nil {
   121  		return path, err
   122  	}
   123  
   124  	return append(path, p...), nil
   125  }
   126  
   127  func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) {
   128  	var path cty.Path
   129  	var err error
   130  
   131  	switch {
   132  	case ty.IsPrimitiveType():
   133  		err = fmt.Errorf("invalid step %q with type %#v", key, ty)
   134  	case ty.IsObjectType():
   135  		path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes())
   136  	case ty.IsTupleType():
   137  		path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes())
   138  	case ty.IsMapType():
   139  		path, err = pathFromFlatmapKeyMap(key, ty)
   140  	case ty.IsListType():
   141  		path, err = pathFromFlatmapKeyList(key, ty)
   142  	case ty.IsSetType():
   143  		path, err = pathFromFlatmapKeySet(key, ty)
   144  	default:
   145  		err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName())
   146  	}
   147  
   148  	if err != nil {
   149  		return path, err
   150  	}
   151  
   152  	return path, nil
   153  }
   154  
   155  func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) {
   156  	var path cty.Path
   157  	var err error
   158  
   159  	k, rest := pathSplit(key)
   160  
   161  	// we don't need to convert the index keys to paths
   162  	if k == "#" {
   163  		return path, nil
   164  	}
   165  
   166  	idx, err := strconv.Atoi(k)
   167  	if err != nil {
   168  		return path, err
   169  	}
   170  
   171  	path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
   172  
   173  	if idx >= len(etys) {
   174  		return path, fmt.Errorf("index %s out of range in %#v", key, etys)
   175  	}
   176  
   177  	if rest == "" {
   178  		return path, nil
   179  	}
   180  
   181  	ty := etys[idx]
   182  
   183  	p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
   184  	if err != nil {
   185  		return path, err
   186  	}
   187  
   188  	return append(path, p...), nil
   189  }
   190  
   191  func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) {
   192  	var path cty.Path
   193  	var err error
   194  
   195  	k, rest := key, ""
   196  	if !ty.ElementType().IsPrimitiveType() {
   197  		k, rest = pathSplit(key)
   198  	}
   199  
   200  	// we don't need to convert the index keys to paths
   201  	if k == "%" {
   202  		return path, nil
   203  	}
   204  
   205  	path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}}
   206  
   207  	if rest == "" {
   208  		return path, nil
   209  	}
   210  
   211  	p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
   212  	if err != nil {
   213  		return path, err
   214  	}
   215  
   216  	return append(path, p...), nil
   217  }
   218  
   219  func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) {
   220  	var path cty.Path
   221  	var err error
   222  
   223  	k, rest := pathSplit(key)
   224  
   225  	// we don't need to convert the index keys to paths
   226  	if key == "#" {
   227  		return path, nil
   228  	}
   229  
   230  	idx, err := strconv.Atoi(k)
   231  	if err != nil {
   232  		return path, err
   233  	}
   234  
   235  	path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
   236  
   237  	if rest == "" {
   238  		return path, nil
   239  	}
   240  
   241  	p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
   242  	if err != nil {
   243  		return path, err
   244  	}
   245  
   246  	return append(path, p...), nil
   247  }
   248  
   249  func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) {
   250  	// once we hit a set, we can't return consistent paths, so just mark the
   251  	// set as a whole changed.
   252  	return nil, nil
   253  }
   254  
   255  // FlatmapKeyFromPath returns the flatmap equivalent of the given cty.Path for
   256  // use in generating legacy style diffs.
   257  func FlatmapKeyFromPath(path cty.Path) string {
   258  	var parts []string
   259  
   260  	for _, step := range path {
   261  		switch step := step.(type) {
   262  		case cty.GetAttrStep:
   263  			parts = append(parts, step.Name)
   264  		case cty.IndexStep:
   265  			switch ty := step.Key.Type(); {
   266  			case ty == cty.String:
   267  				parts = append(parts, step.Key.AsString())
   268  			case ty == cty.Number:
   269  				i, _ := step.Key.AsBigFloat().Int64()
   270  				parts = append(parts, strconv.Itoa(int(i)))
   271  			}
   272  		}
   273  	}
   274  
   275  	return strings.Join(parts, ".")
   276  }