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

     1  package differ
     2  
     3  import (
     4  	"encoding/json"
     5  	"reflect"
     6  
     7  	"github.com/hashicorp/terraform/internal/command/jsonformat/computed"
     8  	"github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path"
     9  	"github.com/hashicorp/terraform/internal/command/jsonplan"
    10  	"github.com/hashicorp/terraform/internal/command/jsonstate"
    11  	viewsjson "github.com/hashicorp/terraform/internal/command/views/json"
    12  	"github.com/hashicorp/terraform/internal/plans"
    13  )
    14  
    15  // Change contains the unmarshalled generic interface{} types that are output by
    16  // the JSON functions in the various json packages (such as jsonplan and
    17  // jsonprovider).
    18  //
    19  // A Change can be converted into a computed.Diff, ready for rendering, with the
    20  // ComputeDiffForAttribute, ComputeDiffForOutput, and ComputeDiffForBlock
    21  // functions.
    22  //
    23  // The Before and After fields are actually go-cty values, but we cannot convert
    24  // them directly because of the Terraform Cloud redacted endpoint. The redacted
    25  // endpoint turns sensitive values into strings regardless of their types.
    26  // Because of this, we cannot just do a direct conversion using the ctyjson
    27  // package. We would have to iterate through the schema first, find the
    28  // sensitive values and their mapped types, update the types inside the schema
    29  // to strings, and then go back and do the overall conversion. This isn't
    30  // including any of the more complicated parts around what happens if something
    31  // was sensitive before and isn't sensitive after or vice versa. This would mean
    32  // the type would need to change between the before and after value. It is in
    33  // fact just easier to iterate through the values as generic JSON interfaces.
    34  type Change struct {
    35  
    36  	// BeforeExplicit matches AfterExplicit except references the Before value.
    37  	BeforeExplicit bool
    38  
    39  	// AfterExplicit refers to whether the After value is explicit or
    40  	// implicit. It is explicit if it has been specified by the user, and
    41  	// implicit if it has been set as a consequence of other changes.
    42  	//
    43  	// For example, explicitly setting a value to null in a list should result
    44  	// in After being null and AfterExplicit being true. In comparison,
    45  	// removing an element from a list should also result in After being null
    46  	// and AfterExplicit being false. Without the explicit information our
    47  	// functions would not be able to tell the difference between these two
    48  	// cases.
    49  	AfterExplicit bool
    50  
    51  	// Before contains the value before the proposed change.
    52  	//
    53  	// The type of the value should be informed by the schema and cast
    54  	// appropriately when needed.
    55  	Before interface{}
    56  
    57  	// After contains the value after the proposed change.
    58  	//
    59  	// The type of the value should be informed by the schema and cast
    60  	// appropriately when needed.
    61  	After interface{}
    62  
    63  	// Unknown describes whether the After value is known or unknown at the time
    64  	// of the plan. In practice, this means the after value should be rendered
    65  	// simply as `(known after apply)`.
    66  	//
    67  	// The concrete value could be a boolean describing whether the entirety of
    68  	// the After value is unknown, or it could be a list or a map depending on
    69  	// the schema describing whether specific elements or attributes within the
    70  	// value are unknown.
    71  	Unknown interface{}
    72  
    73  	// BeforeSensitive matches Unknown, but references whether the Before value
    74  	// is sensitive.
    75  	BeforeSensitive interface{}
    76  
    77  	// AfterSensitive matches Unknown, but references whether the After value is
    78  	// sensitive.
    79  	AfterSensitive interface{}
    80  
    81  	// ReplacePaths contains a set of paths that point to attributes/elements
    82  	// that are causing the overall resource to be replaced rather than simply
    83  	// updated.
    84  	ReplacePaths attribute_path.Matcher
    85  
    86  	// RelevantAttributes contains a set of paths that point attributes/elements
    87  	// that we should display. Any element/attribute not matched by this Matcher
    88  	// should be skipped.
    89  	RelevantAttributes attribute_path.Matcher
    90  }
    91  
    92  // FromJsonChange unmarshals the raw []byte values in the jsonplan.Change
    93  // structs into generic interface{} types that can be reasoned about.
    94  func FromJsonChange(change jsonplan.Change, relevantAttributes attribute_path.Matcher) Change {
    95  	return Change{
    96  		Before:             unmarshalGeneric(change.Before),
    97  		After:              unmarshalGeneric(change.After),
    98  		Unknown:            unmarshalGeneric(change.AfterUnknown),
    99  		BeforeSensitive:    unmarshalGeneric(change.BeforeSensitive),
   100  		AfterSensitive:     unmarshalGeneric(change.AfterSensitive),
   101  		ReplacePaths:       attribute_path.Parse(change.ReplacePaths, false),
   102  		RelevantAttributes: relevantAttributes,
   103  	}
   104  }
   105  
   106  // FromJsonResource unmarshals the raw values in the jsonstate.Resource structs
   107  // into generic interface{} types that can be reasoned about.
   108  func FromJsonResource(resource jsonstate.Resource) Change {
   109  	return Change{
   110  		// We model resource formatting as NoOps.
   111  		Before: unwrapAttributeValues(resource.AttributeValues),
   112  		After:  unwrapAttributeValues(resource.AttributeValues),
   113  
   114  		// We have some sensitive values, but we don't have any unknown values.
   115  		Unknown:         false,
   116  		BeforeSensitive: unmarshalGeneric(resource.SensitiveValues),
   117  		AfterSensitive:  unmarshalGeneric(resource.SensitiveValues),
   118  
   119  		// We don't display replacement data for resources, and all attributes
   120  		// are relevant.
   121  		ReplacePaths:       attribute_path.Empty(false),
   122  		RelevantAttributes: attribute_path.AlwaysMatcher(),
   123  	}
   124  }
   125  
   126  // FromJsonOutput unmarshals the raw values in the jsonstate.Output structs into
   127  // generic interface{} types that can be reasoned about.
   128  func FromJsonOutput(output jsonstate.Output) Change {
   129  	return Change{
   130  		// We model resource formatting as NoOps.
   131  		Before: unmarshalGeneric(output.Value),
   132  		After:  unmarshalGeneric(output.Value),
   133  
   134  		// We have some sensitive values, but we don't have any unknown values.
   135  		Unknown:         false,
   136  		BeforeSensitive: output.Sensitive,
   137  		AfterSensitive:  output.Sensitive,
   138  
   139  		// We don't display replacement data for resources, and all attributes
   140  		// are relevant.
   141  		ReplacePaths:       attribute_path.Empty(false),
   142  		RelevantAttributes: attribute_path.AlwaysMatcher(),
   143  	}
   144  }
   145  
   146  // FromJsonViewsOutput unmarshals the raw values in the viewsjson.Output structs into
   147  // generic interface{} types that can be reasoned about.
   148  func FromJsonViewsOutput(output viewsjson.Output) Change {
   149  	return Change{
   150  		// We model resource formatting as NoOps.
   151  		Before: unmarshalGeneric(output.Value),
   152  		After:  unmarshalGeneric(output.Value),
   153  
   154  		// We have some sensitive values, but we don't have any unknown values.
   155  		Unknown:         false,
   156  		BeforeSensitive: output.Sensitive,
   157  		AfterSensitive:  output.Sensitive,
   158  
   159  		// We don't display replacement data for resources, and all attributes
   160  		// are relevant.
   161  		ReplacePaths:       attribute_path.Empty(false),
   162  		RelevantAttributes: attribute_path.AlwaysMatcher(),
   163  	}
   164  }
   165  
   166  func (change Change) asDiff(renderer computed.DiffRenderer) computed.Diff {
   167  	return computed.NewDiff(renderer, change.calculateChange(), change.ReplacePaths.Matches())
   168  }
   169  
   170  func (change Change) calculateChange() plans.Action {
   171  	if (change.Before == nil && !change.BeforeExplicit) && (change.After != nil || change.AfterExplicit) {
   172  		return plans.Create
   173  	}
   174  	if (change.After == nil && !change.AfterExplicit) && (change.Before != nil || change.BeforeExplicit) {
   175  		return plans.Delete
   176  	}
   177  
   178  	if reflect.DeepEqual(change.Before, change.After) && change.AfterExplicit == change.BeforeExplicit && change.isAfterSensitive() == change.isBeforeSensitive() {
   179  		return plans.NoOp
   180  	}
   181  
   182  	return plans.Update
   183  }
   184  
   185  // getDefaultActionForIteration is used to guess what the change could be for
   186  // complex attributes (collections and objects) and blocks.
   187  //
   188  // You can't really tell the difference between a NoOp and an Update just by
   189  // looking at the attribute itself as you need to inspect the children.
   190  //
   191  // This function returns a Delete or a Create action if the before or after
   192  // values were null, and returns a NoOp for all other cases. It should be used
   193  // in conjunction with compareActions to calculate the actual action based on
   194  // the actions of the children.
   195  func (change Change) getDefaultActionForIteration() plans.Action {
   196  	if change.Before == nil && change.After == nil {
   197  		return plans.NoOp
   198  	}
   199  
   200  	if change.Before == nil {
   201  		return plans.Create
   202  	}
   203  	if change.After == nil {
   204  		return plans.Delete
   205  	}
   206  	return plans.NoOp
   207  }
   208  
   209  // AsNoOp returns the current change as if it is a NoOp operation.
   210  //
   211  // Basically it replaces all the after values with the before values.
   212  func (change Change) AsNoOp() Change {
   213  	return Change{
   214  		BeforeExplicit:     change.BeforeExplicit,
   215  		AfterExplicit:      change.BeforeExplicit,
   216  		Before:             change.Before,
   217  		After:              change.Before,
   218  		Unknown:            false,
   219  		BeforeSensitive:    change.BeforeSensitive,
   220  		AfterSensitive:     change.BeforeSensitive,
   221  		ReplacePaths:       change.ReplacePaths,
   222  		RelevantAttributes: change.RelevantAttributes,
   223  	}
   224  }
   225  
   226  func unmarshalGeneric(raw json.RawMessage) interface{} {
   227  	if raw == nil {
   228  		return nil
   229  	}
   230  
   231  	var out interface{}
   232  	if err := json.Unmarshal(raw, &out); err != nil {
   233  		panic("unrecognized json type: " + err.Error())
   234  	}
   235  	return out
   236  }
   237  
   238  func unwrapAttributeValues(values jsonstate.AttributeValues) map[string]interface{} {
   239  	out := make(map[string]interface{})
   240  	for key, value := range values {
   241  		out[key] = unmarshalGeneric(value)
   242  	}
   243  	return out
   244  }