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 }