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 }