mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-26 17:01:04 -06:00
cb2e9119aa
Signed-off-by: namgyalangmo <75657887+namgyalangmo@users.noreply.github.com>
282 lines
6.4 KiB
Go
282 lines
6.4 KiB
Go
// Copyright (c) The OpenTofu Authors
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) 2023 HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package hcl2shim
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// RequiresReplace takes a list of flatmapped paths from a
|
|
// InstanceDiff.Attributes along with the corresponding cty.Type, and returns
|
|
// the list of the cty.Paths that are flagged as causing the resource
|
|
// replacement (RequiresNew).
|
|
// This will filter out redundant paths, paths that refer to flatmapped indexes
|
|
// (e.g. "#", "%"), and will return any changes within a set as the path to the
|
|
// set itself.
|
|
func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) {
|
|
var paths []cty.Path
|
|
|
|
for _, attr := range attrs {
|
|
p, err := requiresReplacePath(attr, ty)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
paths = append(paths, p)
|
|
}
|
|
|
|
// now trim off any trailing paths that aren't GetAttrSteps, since only an
|
|
// attribute itself can require replacement
|
|
paths = trimPaths(paths)
|
|
|
|
// There may be redundant paths due to set elements or index attributes
|
|
// Do some ugly n^2 filtering, but these are always fairly small sets.
|
|
for i := 0; i < len(paths)-1; i++ {
|
|
for j := i + 1; j < len(paths); j++ {
|
|
if reflect.DeepEqual(paths[i], paths[j]) {
|
|
// swap the tail and slice it off
|
|
paths[j], paths[len(paths)-1] = paths[len(paths)-1], paths[j]
|
|
paths = paths[:len(paths)-1]
|
|
j--
|
|
}
|
|
}
|
|
}
|
|
|
|
return paths, nil
|
|
}
|
|
|
|
// trimPaths removes any trailing steps that aren't of type GetAttrSet, since
|
|
// only an attribute itself can require replacement
|
|
func trimPaths(paths []cty.Path) []cty.Path {
|
|
var trimmed []cty.Path
|
|
for _, path := range paths {
|
|
path = trimPath(path)
|
|
if len(path) > 0 {
|
|
trimmed = append(trimmed, path)
|
|
}
|
|
}
|
|
return trimmed
|
|
}
|
|
|
|
func trimPath(path cty.Path) cty.Path {
|
|
for len(path) > 0 {
|
|
_, isGetAttr := path[len(path)-1].(cty.GetAttrStep)
|
|
if isGetAttr {
|
|
break
|
|
}
|
|
path = path[:len(path)-1]
|
|
}
|
|
return path
|
|
}
|
|
|
|
// requiresReplacePath takes a key from a flatmap along with the cty.Type
|
|
// describing the structure, and returns the cty.Path that would be used to
|
|
// reference the nested value in the data structure.
|
|
// This is used specifically to record the RequiresReplace attributes from a
|
|
// ResourceInstanceDiff.
|
|
func requiresReplacePath(k string, ty cty.Type) (cty.Path, error) {
|
|
if k == "" {
|
|
return nil, nil
|
|
}
|
|
if !ty.IsObjectType() {
|
|
panic(fmt.Sprintf("requires replace path on non-object type: %#v", ty))
|
|
}
|
|
|
|
path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes())
|
|
if err != nil {
|
|
return path, fmt.Errorf("[%s] %w", k, err)
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
func pathSplit(p string) (string, string) {
|
|
parts := strings.SplitN(p, ".", 2)
|
|
head := parts[0]
|
|
rest := ""
|
|
if len(parts) > 1 {
|
|
rest = parts[1]
|
|
}
|
|
return head, rest
|
|
}
|
|
|
|
func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) {
|
|
k, rest := pathSplit(key)
|
|
|
|
path := cty.Path{cty.GetAttrStep{Name: k}}
|
|
|
|
ty, ok := atys[k]
|
|
if !ok {
|
|
return path, fmt.Errorf("attribute %q not found", k)
|
|
}
|
|
|
|
if rest == "" {
|
|
return path, nil
|
|
}
|
|
|
|
p, err := pathFromFlatmapKeyValue(rest, ty)
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
return append(path, p...), nil
|
|
}
|
|
|
|
func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) {
|
|
var path cty.Path
|
|
var err error
|
|
|
|
switch {
|
|
case ty.IsPrimitiveType():
|
|
err = fmt.Errorf("invalid step %q with type %#v", key, ty)
|
|
case ty.IsObjectType():
|
|
path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes())
|
|
case ty.IsTupleType():
|
|
path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes())
|
|
case ty.IsMapType():
|
|
path, err = pathFromFlatmapKeyMap(key, ty)
|
|
case ty.IsListType():
|
|
path, err = pathFromFlatmapKeyList(key, ty)
|
|
case ty.IsSetType():
|
|
path, err = pathFromFlatmapKeySet(key, ty)
|
|
default:
|
|
err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName())
|
|
}
|
|
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
return path, nil
|
|
}
|
|
|
|
func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) {
|
|
var path cty.Path
|
|
var err error
|
|
|
|
k, rest := pathSplit(key)
|
|
|
|
// we don't need to convert the index keys to paths
|
|
if k == "#" {
|
|
return path, nil
|
|
}
|
|
|
|
idx, err := strconv.Atoi(k)
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
|
|
|
|
if idx >= len(etys) {
|
|
return path, fmt.Errorf("index %s out of range in %#v", key, etys)
|
|
}
|
|
|
|
if rest == "" {
|
|
return path, nil
|
|
}
|
|
|
|
ty := etys[idx]
|
|
|
|
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
return append(path, p...), nil
|
|
}
|
|
|
|
func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) {
|
|
var path cty.Path
|
|
var err error
|
|
|
|
k, rest := key, ""
|
|
if !ty.ElementType().IsPrimitiveType() {
|
|
k, rest = pathSplit(key)
|
|
}
|
|
|
|
// we don't need to convert the index keys to paths
|
|
if k == "%" {
|
|
return path, nil
|
|
}
|
|
|
|
path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}}
|
|
|
|
if rest == "" {
|
|
return path, nil
|
|
}
|
|
|
|
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
return append(path, p...), nil
|
|
}
|
|
|
|
func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) {
|
|
var path cty.Path
|
|
var err error
|
|
|
|
k, rest := pathSplit(key)
|
|
|
|
// we don't need to convert the index keys to paths
|
|
if key == "#" {
|
|
return path, nil
|
|
}
|
|
|
|
idx, err := strconv.Atoi(k)
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}}
|
|
|
|
if rest == "" {
|
|
return path, nil
|
|
}
|
|
|
|
p, err := pathFromFlatmapKeyValue(rest, ty.ElementType())
|
|
if err != nil {
|
|
return path, err
|
|
}
|
|
|
|
return append(path, p...), nil
|
|
}
|
|
|
|
func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) {
|
|
// once we hit a set, we can't return consistent paths, so just mark the
|
|
// set as a whole changed.
|
|
return nil, nil
|
|
}
|
|
|
|
// FlatmapKeyFromPath returns the flatmap equivalent of the given cty.Path for
|
|
// use in generating legacy style diffs.
|
|
func FlatmapKeyFromPath(path cty.Path) string {
|
|
var parts []string
|
|
|
|
for _, step := range path {
|
|
switch step := step.(type) {
|
|
case cty.GetAttrStep:
|
|
parts = append(parts, step.Name)
|
|
case cty.IndexStep:
|
|
switch ty := step.Key.Type(); {
|
|
case ty == cty.String:
|
|
parts = append(parts, step.Key.AsString())
|
|
case ty == cty.Number:
|
|
i, _ := step.Key.AsBigFloat().Int64()
|
|
parts = append(parts, strconv.Itoa(int(i)))
|
|
}
|
|
}
|
|
}
|
|
|
|
return strings.Join(parts, ".")
|
|
}
|