github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/structured/change.go (about)

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