github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/objchange/plan_valid.go (about) 1 package objchange 2 3 import ( 4 "fmt" 5 6 "github.com/zclconf/go-cty/cty" 7 8 "github.com/hashicorp/terraform/internal/configs/configschema" 9 ) 10 11 // AssertPlanValid checks checks whether a planned new state returned by a 12 // provider's PlanResourceChange method is suitable to achieve a change 13 // from priorState to config. It returns a slice with nonzero length if 14 // any problems are detected. Because problems here indicate bugs in the 15 // provider that generated the plannedState, they are written with provider 16 // developers as an audience, rather than end-users. 17 // 18 // All of the given values must have the same type and must conform to the 19 // implied type of the given schema, or this function may panic or produce 20 // garbage results. 21 // 22 // During planning, a provider may only make changes to attributes that are 23 // null (unset) in the configuration and are marked as "computed" in the 24 // resource type schema, in order to insert any default values the provider 25 // may know about. If the default value cannot be determined until apply time, 26 // the provider can return an unknown value. Providers are forbidden from 27 // planning a change that disagrees with any non-null argument in the 28 // configuration. 29 // 30 // As a special exception, providers _are_ allowed to provide attribute values 31 // conflicting with configuration if and only if the planned value exactly 32 // matches the corresponding attribute value in the prior state. The provider 33 // can use this to signal that the new value is functionally equivalent to 34 // the old and thus no change is required. 35 func AssertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value) []error { 36 return assertPlanValid(schema, priorState, config, plannedState, nil) 37 } 38 39 func assertPlanValid(schema *configschema.Block, priorState, config, plannedState cty.Value, path cty.Path) []error { 40 var errs []error 41 if plannedState.IsNull() && !config.IsNull() { 42 errs = append(errs, path.NewErrorf("planned for absence but config wants existence")) 43 return errs 44 } 45 if config.IsNull() && !plannedState.IsNull() { 46 errs = append(errs, path.NewErrorf("planned for existence but config wants absence")) 47 return errs 48 } 49 if plannedState.IsNull() { 50 // No further checks possible if the planned value is null 51 return errs 52 } 53 54 impTy := schema.ImpliedType() 55 56 // verify attributes 57 moreErrs := assertPlannedAttrsValid(schema.Attributes, priorState, config, plannedState, path) 58 errs = append(errs, moreErrs...) 59 60 for name, blockS := range schema.BlockTypes { 61 path := append(path, cty.GetAttrStep{Name: name}) 62 plannedV := plannedState.GetAttr(name) 63 configV := config.GetAttr(name) 64 priorV := cty.NullVal(impTy.AttributeType(name)) 65 if !priorState.IsNull() { 66 priorV = priorState.GetAttr(name) 67 } 68 if plannedV.RawEquals(configV) { 69 // Easy path: nothing has changed at all 70 continue 71 } 72 73 if !configV.IsKnown() { 74 // An unknown config block represents a dynamic block where the 75 // for_each value is unknown, and therefor cannot be altered by the 76 // provider. 77 errs = append(errs, path.NewErrorf("planned value %#v for unknown dynamic block", plannedV)) 78 continue 79 } 80 81 if !plannedV.IsKnown() { 82 // Only dynamic configuration can set blocks to unknown, so this is 83 // not allowed from the provider. This means that either the config 84 // and plan should match, or we have an error where the plan 85 // changed the config value, both of which have been checked. 86 errs = append(errs, path.NewErrorf("attribute representing nested block must not be unknown itself; set nested attribute values to unknown instead")) 87 continue 88 } 89 90 switch blockS.Nesting { 91 case configschema.NestingSingle, configschema.NestingGroup: 92 moreErrs := assertPlanValid(&blockS.Block, priorV, configV, plannedV, path) 93 errs = append(errs, moreErrs...) 94 case configschema.NestingList: 95 // A NestingList might either be a list or a tuple, depending on 96 // whether there are dynamically-typed attributes inside. However, 97 // both support a similar-enough API that we can treat them the 98 // same for our purposes here. 99 if plannedV.IsNull() { 100 errs = append(errs, path.NewErrorf("attribute representing a list of nested blocks must be empty to indicate no blocks, not null")) 101 continue 102 } 103 104 if configV.IsNull() { 105 // Configuration cannot decode a block into a null value, but 106 // we could be dealing with a null returned by a legacy 107 // provider and inserted via ignore_changes. Fix the value in 108 // place so the length can still be compared. 109 configV = cty.ListValEmpty(configV.Type().ElementType()) 110 } 111 112 plannedL := plannedV.LengthInt() 113 configL := configV.LengthInt() 114 if plannedL != configL { 115 errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) 116 continue 117 } 118 119 for it := plannedV.ElementIterator(); it.Next(); { 120 idx, plannedEV := it.Element() 121 path := append(path, cty.IndexStep{Key: idx}) 122 if !plannedEV.IsKnown() { 123 errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) 124 continue 125 } 126 if !configV.HasIndex(idx).True() { 127 continue // should never happen since we checked the lengths above 128 } 129 configEV := configV.Index(idx) 130 priorEV := cty.NullVal(blockS.ImpliedType()) 131 if !priorV.IsNull() && priorV.HasIndex(idx).True() { 132 priorEV = priorV.Index(idx) 133 } 134 135 moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) 136 errs = append(errs, moreErrs...) 137 } 138 case configschema.NestingMap: 139 if plannedV.IsNull() { 140 errs = append(errs, path.NewErrorf("attribute representing a map of nested blocks must be empty to indicate no blocks, not null")) 141 continue 142 } 143 144 // A NestingMap might either be a map or an object, depending on 145 // whether there are dynamically-typed attributes inside, but 146 // that's decided statically and so all values will have the same 147 // kind. 148 if plannedV.Type().IsObjectType() { 149 plannedAtys := plannedV.Type().AttributeTypes() 150 configAtys := configV.Type().AttributeTypes() 151 for k := range plannedAtys { 152 if _, ok := configAtys[k]; !ok { 153 errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k)) 154 continue 155 } 156 path := append(path, cty.GetAttrStep{Name: k}) 157 158 plannedEV := plannedV.GetAttr(k) 159 if !plannedEV.IsKnown() { 160 errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) 161 continue 162 } 163 configEV := configV.GetAttr(k) 164 priorEV := cty.NullVal(blockS.ImpliedType()) 165 if !priorV.IsNull() && priorV.Type().HasAttribute(k) { 166 priorEV = priorV.GetAttr(k) 167 } 168 moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) 169 errs = append(errs, moreErrs...) 170 } 171 for k := range configAtys { 172 if _, ok := plannedAtys[k]; !ok { 173 errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", k)) 174 continue 175 } 176 } 177 } else { 178 plannedL := plannedV.LengthInt() 179 configL := configV.LengthInt() 180 if plannedL != configL { 181 errs = append(errs, path.NewErrorf("block count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) 182 continue 183 } 184 for it := plannedV.ElementIterator(); it.Next(); { 185 idx, plannedEV := it.Element() 186 path := append(path, cty.IndexStep{Key: idx}) 187 if !plannedEV.IsKnown() { 188 errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) 189 continue 190 } 191 k := idx.AsString() 192 if !configV.HasIndex(idx).True() { 193 errs = append(errs, path.NewErrorf("block key %q from plan is not present in config", k)) 194 continue 195 } 196 configEV := configV.Index(idx) 197 priorEV := cty.NullVal(blockS.ImpliedType()) 198 if !priorV.IsNull() && priorV.HasIndex(idx).True() { 199 priorEV = priorV.Index(idx) 200 } 201 moreErrs := assertPlanValid(&blockS.Block, priorEV, configEV, plannedEV, path) 202 errs = append(errs, moreErrs...) 203 } 204 for it := configV.ElementIterator(); it.Next(); { 205 idx, _ := it.Element() 206 if !plannedV.HasIndex(idx).True() { 207 errs = append(errs, path.NewErrorf("block key %q from config is not present in plan", idx.AsString())) 208 continue 209 } 210 } 211 } 212 case configschema.NestingSet: 213 if plannedV.IsNull() { 214 errs = append(errs, path.NewErrorf("attribute representing a set of nested blocks must be empty to indicate no blocks, not null")) 215 continue 216 } 217 218 // Because set elements have no identifier with which to correlate 219 // them, we can't robustly validate the plan for a nested block 220 // backed by a set, and so unfortunately we need to just trust the 221 // provider to do the right thing. :( 222 // 223 // (In principle we could correlate elements by matching the 224 // subset of attributes explicitly set in config, except for the 225 // special diff suppression rule which allows for there to be a 226 // planned value that is constructed by mixing part of a prior 227 // value with part of a config value, creating an entirely new 228 // element that is not present in either prior nor config.) 229 for it := plannedV.ElementIterator(); it.Next(); { 230 idx, plannedEV := it.Element() 231 path := append(path, cty.IndexStep{Key: idx}) 232 if !plannedEV.IsKnown() { 233 errs = append(errs, path.NewErrorf("element representing nested block must not be unknown itself; set nested attribute values to unknown instead")) 234 continue 235 } 236 } 237 238 default: 239 panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting)) 240 } 241 } 242 243 return errs 244 } 245 246 func assertPlannedAttrsValid(schema map[string]*configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error { 247 var errs []error 248 for name, attrS := range schema { 249 moreErrs := assertPlannedAttrValid(name, attrS, priorState, config, plannedState, path) 250 errs = append(errs, moreErrs...) 251 } 252 return errs 253 } 254 255 func assertPlannedAttrValid(name string, attrS *configschema.Attribute, priorState, config, plannedState cty.Value, path cty.Path) []error { 256 plannedV := plannedState.GetAttr(name) 257 configV := config.GetAttr(name) 258 priorV := cty.NullVal(attrS.Type) 259 if !priorState.IsNull() { 260 priorV = priorState.GetAttr(name) 261 } 262 path = append(path, cty.GetAttrStep{Name: name}) 263 264 return assertPlannedValueValid(attrS, priorV, configV, plannedV, path) 265 } 266 267 func assertPlannedValueValid(attrS *configschema.Attribute, priorV, configV, plannedV cty.Value, path cty.Path) []error { 268 269 var errs []error 270 if plannedV.RawEquals(configV) { 271 // This is the easy path: provider didn't change anything at all. 272 return errs 273 } 274 if plannedV.RawEquals(priorV) && !priorV.IsNull() && !configV.IsNull() { 275 // Also pretty easy: there is a prior value and the provider has 276 // returned it unchanged. This indicates that configV and plannedV 277 // are functionally equivalent and so the provider wishes to disregard 278 // the configuration value in favor of the prior. 279 return errs 280 } 281 282 switch { 283 // The provider can plan any value for a computed-only attribute. There may 284 // be a config value here in the case where a user used `ignore_changes` on 285 // a computed attribute and ignored the warning, or we failed to validate 286 // computed attributes in the config, but regardless it's not a plan error 287 // caused by the provider. 288 case attrS.Computed && !attrS.Optional: 289 return errs 290 291 // The provider is allowed to insert optional values when the config is 292 // null, but only if the attribute is computed. 293 case configV.IsNull() && attrS.Computed: 294 return errs 295 296 case configV.IsNull() && !plannedV.IsNull(): 297 // if the attribute is not computed, then any planned value is incorrect 298 if attrS.Sensitive { 299 errs = append(errs, path.NewErrorf("sensitive planned value for a non-computed attribute")) 300 } else { 301 errs = append(errs, path.NewErrorf("planned value %#v for a non-computed attribute", plannedV)) 302 } 303 return errs 304 } 305 306 // If this attribute has a NestedType, validate the nested object 307 if attrS.NestedType != nil { 308 return assertPlannedObjectValid(attrS.NestedType, priorV, configV, plannedV, path) 309 } 310 311 // If none of the above conditions match, the provider has made an invalid 312 // change to this attribute. 313 if priorV.IsNull() { 314 if attrS.Sensitive { 315 errs = append(errs, path.NewErrorf("sensitive planned value does not match config value")) 316 } else { 317 errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v", plannedV, configV)) 318 } 319 return errs 320 } 321 322 if attrS.Sensitive { 323 errs = append(errs, path.NewErrorf("sensitive planned value does not match config value nor prior value")) 324 } else { 325 errs = append(errs, path.NewErrorf("planned value %#v does not match config value %#v nor prior value %#v", plannedV, configV, priorV)) 326 } 327 328 return errs 329 } 330 331 func assertPlannedObjectValid(schema *configschema.Object, prior, config, planned cty.Value, path cty.Path) []error { 332 var errs []error 333 334 if planned.IsNull() && !config.IsNull() { 335 errs = append(errs, path.NewErrorf("planned for absence but config wants existence")) 336 return errs 337 } 338 if config.IsNull() && !planned.IsNull() { 339 errs = append(errs, path.NewErrorf("planned for existence but config wants absence")) 340 return errs 341 } 342 if planned.IsNull() { 343 // No further checks possible if the planned value is null 344 return errs 345 } 346 347 switch schema.Nesting { 348 case configschema.NestingSingle, configschema.NestingGroup: 349 moreErrs := assertPlannedAttrsValid(schema.Attributes, prior, config, planned, path) 350 errs = append(errs, moreErrs...) 351 352 case configschema.NestingList: 353 // A NestingList might either be a list or a tuple, depending on 354 // whether there are dynamically-typed attributes inside. However, 355 // both support a similar-enough API that we can treat them the 356 // same for our purposes here. 357 358 plannedL := planned.LengthInt() 359 configL := config.LengthInt() 360 if plannedL != configL { 361 errs = append(errs, path.NewErrorf("count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) 362 return errs 363 } 364 for it := planned.ElementIterator(); it.Next(); { 365 idx, plannedEV := it.Element() 366 path := append(path, cty.IndexStep{Key: idx}) 367 if !config.HasIndex(idx).True() { 368 continue // should never happen since we checked the lengths above 369 } 370 configEV := config.Index(idx) 371 priorEV := cty.NullVal(schema.ImpliedType()) 372 if !prior.IsNull() && prior.HasIndex(idx).True() { 373 priorEV = prior.Index(idx) 374 } 375 376 moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path) 377 errs = append(errs, moreErrs...) 378 } 379 380 case configschema.NestingMap: 381 // A NestingMap might either be a map or an object, depending on 382 // whether there are dynamically-typed attributes inside, so we will 383 // break these down to maps to handle them both in the same manner. 384 plannedVals := map[string]cty.Value{} 385 configVals := map[string]cty.Value{} 386 priorVals := map[string]cty.Value{} 387 388 if !planned.IsNull() { 389 plannedVals = planned.AsValueMap() 390 } 391 if !config.IsNull() { 392 configVals = config.AsValueMap() 393 } 394 if !prior.IsNull() { 395 priorVals = prior.AsValueMap() 396 } 397 398 for k, plannedEV := range plannedVals { 399 configEV, ok := configVals[k] 400 if !ok { 401 errs = append(errs, path.NewErrorf("map key %q from plan is not present in config", k)) 402 continue 403 } 404 path := append(path, cty.GetAttrStep{Name: k}) 405 406 priorEV, ok := priorVals[k] 407 if !ok { 408 priorEV = cty.NullVal(schema.ImpliedType()) 409 } 410 moreErrs := assertPlannedAttrsValid(schema.Attributes, priorEV, configEV, plannedEV, path) 411 errs = append(errs, moreErrs...) 412 } 413 for k := range configVals { 414 if _, ok := plannedVals[k]; !ok { 415 errs = append(errs, path.NewErrorf("map key %q from config is not present in plan", k)) 416 continue 417 } 418 } 419 420 case configschema.NestingSet: 421 plannedL := planned.LengthInt() 422 configL := config.LengthInt() 423 if plannedL != configL { 424 errs = append(errs, path.NewErrorf("count in plan (%d) disagrees with count in config (%d)", plannedL, configL)) 425 return errs 426 } 427 // Because set elements have no identifier with which to correlate 428 // them, we can't robustly validate the plan for a nested object 429 // backed by a set, and so unfortunately we need to just trust the 430 // provider to do the right thing. 431 } 432 433 return errs 434 }