github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/jsondiff/diff.go (about)

     1  package jsondiff
     2  
     3  import (
     4  	"reflect"
     5  
     6  	"github.com/zclconf/go-cty/cty"
     7  
     8  	"github.com/hashicorp/terraform/internal/command/jsonformat/collections"
     9  	"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
    10  	"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
    11  	"github.com/hashicorp/terraform/internal/plans"
    12  )
    13  
    14  type TransformPrimitiveJson func(before, after interface{}, ctype cty.Type, action plans.Action) computed.Diff
    15  type TransformObjectJson func(map[string]computed.Diff, plans.Action) computed.Diff
    16  type TransformArrayJson func([]computed.Diff, plans.Action) computed.Diff
    17  type TransformTypeChangeJson func(before, after computed.Diff, action plans.Action) computed.Diff
    18  
    19  // JsonOpts defines the external callback functions that callers should
    20  // implement to process the supplied diffs.
    21  type JsonOpts struct {
    22  	Primitive  TransformPrimitiveJson
    23  	Object     TransformObjectJson
    24  	Array      TransformArrayJson
    25  	TypeChange TransformTypeChangeJson
    26  }
    27  
    28  // Transform accepts a generic before and after value that is assumed to be JSON
    29  // formatted and transforms it into a computed.Diff, using the callbacks
    30  // supplied in the JsonOpts class.
    31  func (opts JsonOpts) Transform(before, after interface{}, beforeExplicit, afterExplicit bool, relevantAttributes attribute_path.Matcher) computed.Diff {
    32  	beforeType := GetType(before)
    33  	afterType := GetType(after)
    34  
    35  	deleted := afterType == Null && !afterExplicit
    36  	created := beforeType == Null && !beforeExplicit
    37  
    38  	if beforeType == afterType || (created || deleted) {
    39  		targetType := beforeType
    40  		if targetType == Null {
    41  			targetType = afterType
    42  		}
    43  		return opts.processUpdate(before, after, beforeExplicit, afterExplicit, targetType, relevantAttributes)
    44  	}
    45  
    46  	b := opts.processUpdate(before, nil, beforeExplicit, false, beforeType, relevantAttributes)
    47  	a := opts.processUpdate(nil, after, false, afterExplicit, afterType, relevantAttributes)
    48  	return opts.TypeChange(b, a, plans.Update)
    49  }
    50  
    51  func (opts JsonOpts) processUpdate(before, after interface{}, beforeExplicit, afterExplicit bool, jtype Type, relevantAttributes attribute_path.Matcher) computed.Diff {
    52  	switch jtype {
    53  	case Null:
    54  		return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.NilType)
    55  	case Bool:
    56  		return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.Bool)
    57  	case String:
    58  		return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.String)
    59  	case Number:
    60  		return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.Number)
    61  	case Object:
    62  		var b, a map[string]interface{}
    63  
    64  		if before != nil {
    65  			b = before.(map[string]interface{})
    66  		}
    67  
    68  		if after != nil {
    69  			a = after.(map[string]interface{})
    70  		}
    71  
    72  		return opts.processObject(b, a, relevantAttributes)
    73  	case Array:
    74  		var b, a []interface{}
    75  
    76  		if before != nil {
    77  			b = before.([]interface{})
    78  		}
    79  
    80  		if after != nil {
    81  			a = after.([]interface{})
    82  		}
    83  
    84  		return opts.processArray(b, a)
    85  	default:
    86  		panic("unrecognized json type: " + jtype)
    87  	}
    88  }
    89  
    90  func (opts JsonOpts) processPrimitive(before, after interface{}, beforeExplicit, afterExplicit bool, ctype cty.Type) computed.Diff {
    91  	beforeMissing := before == nil && !beforeExplicit
    92  	afterMissing := after == nil && !afterExplicit
    93  
    94  	var action plans.Action
    95  	switch {
    96  	case beforeMissing && !afterMissing:
    97  		action = plans.Create
    98  	case !beforeMissing && afterMissing:
    99  		action = plans.Delete
   100  	case reflect.DeepEqual(before, after):
   101  		action = plans.NoOp
   102  	default:
   103  		action = plans.Update
   104  	}
   105  
   106  	return opts.Primitive(before, after, ctype, action)
   107  }
   108  
   109  func (opts JsonOpts) processArray(before, after []interface{}) computed.Diff {
   110  	processIndices := func(beforeIx, afterIx int) computed.Diff {
   111  		var b, a interface{}
   112  
   113  		beforeExplicit := false
   114  		afterExplicit := false
   115  
   116  		if beforeIx >= 0 && beforeIx < len(before) {
   117  			b = before[beforeIx]
   118  			beforeExplicit = true
   119  		}
   120  		if afterIx >= 0 && afterIx < len(after) {
   121  			a = after[afterIx]
   122  			afterExplicit = true
   123  		}
   124  
   125  		// It's actually really difficult to render the diffs when some indices
   126  		// within a list are relevant and others aren't. To make this simpler
   127  		// we just treat all children of a relevant list as also relevant.
   128  		//
   129  		// Interestingly the terraform plan builder also agrees with this, and
   130  		// never sets relevant attributes beneath lists or sets. We're just
   131  		// going to enforce this logic here as well. If the list is relevant
   132  		// (decided elsewhere), then every element in the list is also relevant.
   133  		return opts.Transform(b, a, beforeExplicit, afterExplicit, attribute_path.AlwaysMatcher())
   134  	}
   135  
   136  	isObjType := func(value interface{}) bool {
   137  		return GetType(value) == Object
   138  	}
   139  
   140  	return opts.Array(collections.TransformSlice(before, after, processIndices, isObjType))
   141  }
   142  
   143  func (opts JsonOpts) processObject(before, after map[string]interface{}, relevantAttributes attribute_path.Matcher) computed.Diff {
   144  	return opts.Object(collections.TransformMap(before, after, func(key string) computed.Diff {
   145  		beforeChild, beforeExplicit := before[key]
   146  		afterChild, afterExplicit := after[key]
   147  
   148  		childRelevantAttributes := relevantAttributes.GetChildWithKey(key)
   149  		if !childRelevantAttributes.MatchesPartial() {
   150  			// Mark non-relevant attributes as unchanged.
   151  			afterChild = beforeChild
   152  			afterExplicit = beforeExplicit
   153  
   154  		}
   155  
   156  		return opts.Transform(beforeChild, afterChild, beforeExplicit, afterExplicit, childRelevantAttributes)
   157  	}))
   158  }