1package terraform 2 3import ( 4 "fmt" 5 "log" 6 "strings" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/hcl2/hcl" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform/addrs" 13 "github.com/hashicorp/terraform/configs" 14 "github.com/hashicorp/terraform/plans" 15 "github.com/hashicorp/terraform/plans/objchange" 16 "github.com/hashicorp/terraform/providers" 17 "github.com/hashicorp/terraform/provisioners" 18 "github.com/hashicorp/terraform/states" 19 "github.com/hashicorp/terraform/tfdiags" 20) 21 22// EvalApply is an EvalNode implementation that writes the diff to 23// the full diff. 24type EvalApply struct { 25 Addr addrs.ResourceInstance 26 Config *configs.Resource 27 Dependencies []addrs.Referenceable 28 State **states.ResourceInstanceObject 29 Change **plans.ResourceInstanceChange 30 ProviderAddr addrs.AbsProviderConfig 31 Provider *providers.Interface 32 ProviderSchema **ProviderSchema 33 Output **states.ResourceInstanceObject 34 CreateNew *bool 35 Error *error 36} 37 38// TODO: test 39func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { 40 var diags tfdiags.Diagnostics 41 42 change := *n.Change 43 provider := *n.Provider 44 state := *n.State 45 absAddr := n.Addr.Absolute(ctx.Path()) 46 47 if state == nil { 48 state = &states.ResourceInstanceObject{} 49 } 50 51 schema, _ := (*n.ProviderSchema).SchemaForResourceType(n.Addr.Resource.Mode, n.Addr.Resource.Type) 52 if schema == nil { 53 // Should be caught during validation, so we don't bother with a pretty error here 54 return nil, fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Type) 55 } 56 57 if n.CreateNew != nil { 58 *n.CreateNew = (change.Action == plans.Create || change.Action.IsReplace()) 59 } 60 61 configVal := cty.NullVal(cty.DynamicPseudoType) 62 if n.Config != nil { 63 var configDiags tfdiags.Diagnostics 64 keyData := EvalDataForInstanceKey(n.Addr.Key) 65 configVal, _, configDiags = ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData) 66 diags = diags.Append(configDiags) 67 if configDiags.HasErrors() { 68 return nil, diags.Err() 69 } 70 } 71 72 log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action) 73 resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ 74 TypeName: n.Addr.Resource.Type, 75 PriorState: change.Before, 76 Config: configVal, 77 PlannedState: change.After, 78 PlannedPrivate: change.Private, 79 }) 80 applyDiags := resp.Diagnostics 81 if n.Config != nil { 82 applyDiags = applyDiags.InConfigBody(n.Config.Config) 83 } 84 diags = diags.Append(applyDiags) 85 86 // Even if there are errors in the returned diagnostics, the provider may 87 // have returned a _partial_ state for an object that already exists but 88 // failed to fully configure, and so the remaining code must always run 89 // to completion but must be defensive against the new value being 90 // incomplete. 91 newVal := resp.NewState 92 93 if newVal == cty.NilVal { 94 // Providers are supposed to return a partial new value even when errors 95 // occur, but sometimes they don't and so in that case we'll patch that up 96 // by just using the prior state, so we'll at least keep track of the 97 // object for the user to retry. 98 newVal = change.Before 99 100 // As a special case, we'll set the new value to null if it looks like 101 // we were trying to execute a delete, because the provider in this case 102 // probably left the newVal unset intending it to be interpreted as "null". 103 if change.After.IsNull() { 104 newVal = cty.NullVal(schema.ImpliedType()) 105 } 106 107 // Ideally we'd produce an error or warning here if newVal is nil and 108 // there are no errors in diags, because that indicates a buggy 109 // provider not properly reporting its result, but unfortunately many 110 // of our historical test mocks behave in this way and so producing 111 // a diagnostic here fails hundreds of tests. Instead, we must just 112 // silently retain the old value for now. Returning a nil value with 113 // no errors is still always considered a bug in the provider though, 114 // and should be fixed for any "real" providers that do it. 115 } 116 117 var conformDiags tfdiags.Diagnostics 118 for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) { 119 conformDiags = conformDiags.Append(tfdiags.Sourceless( 120 tfdiags.Error, 121 "Provider produced invalid object", 122 fmt.Sprintf( 123 "Provider %q produced an invalid value after apply for %s. The result cannot not be saved in the Terraform state.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 124 n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatErrorPrefixed(err, absAddr.String()), 125 ), 126 )) 127 } 128 diags = diags.Append(conformDiags) 129 if conformDiags.HasErrors() { 130 // Bail early in this particular case, because an object that doesn't 131 // conform to the schema can't be saved in the state anyway -- the 132 // serializer will reject it. 133 return nil, diags.Err() 134 } 135 136 // After this point we have a type-conforming result object and so we 137 // must always run to completion to ensure it can be saved. If n.Error 138 // is set then we must not return a non-nil error, in order to allow 139 // evaluation to continue to a later point where our state object will 140 // be saved. 141 142 // By this point there must not be any unknown values remaining in our 143 // object, because we've applied the change and we can't save unknowns 144 // in our persistent state. If any are present then we will indicate an 145 // error (which is always a bug in the provider) but we will also replace 146 // them with nulls so that we can successfully save the portions of the 147 // returned value that are known. 148 if !newVal.IsWhollyKnown() { 149 // To generate better error messages, we'll go for a walk through the 150 // value and make a separate diagnostic for each unknown value we 151 // find. 152 cty.Walk(newVal, func(path cty.Path, val cty.Value) (bool, error) { 153 if !val.IsKnown() { 154 pathStr := tfdiags.FormatCtyPath(path) 155 diags = diags.Append(tfdiags.Sourceless( 156 tfdiags.Error, 157 "Provider returned invalid result object after apply", 158 fmt.Sprintf( 159 "After the apply operation, the provider still indicated an unknown value for %s%s. All values must be known after apply, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save the other known object values in the state.", 160 n.Addr.Absolute(ctx.Path()), pathStr, 161 ), 162 )) 163 } 164 return true, nil 165 }) 166 167 // NOTE: This operation can potentially be lossy if there are multiple 168 // elements in a set that differ only by unknown values: after 169 // replacing with null these will be merged together into a single set 170 // element. Since we can only get here in the presence of a provider 171 // bug, we accept this because storing a result here is always a 172 // best-effort sort of thing. 173 newVal = cty.UnknownAsNull(newVal) 174 } 175 176 if change.Action != plans.Delete && !diags.HasErrors() { 177 // Only values that were marked as unknown in the planned value are allowed 178 // to change during the apply operation. (We do this after the unknown-ness 179 // check above so that we also catch anything that became unknown after 180 // being known during plan.) 181 // 182 // If we are returning other errors anyway then we'll give this 183 // a pass since the other errors are usually the explanation for 184 // this one and so it's more helpful to let the user focus on the 185 // root cause rather than distract with this extra problem. 186 if errs := objchange.AssertObjectCompatible(schema, change.After, newVal); len(errs) > 0 { 187 if resp.LegacyTypeSystem { 188 // The shimming of the old type system in the legacy SDK is not precise 189 // enough to pass this consistency check, so we'll give it a pass here, 190 // but we will generate a warning about it so that we are more likely 191 // to notice in the logs if an inconsistency beyond the type system 192 // leads to a downstream provider failure. 193 var buf strings.Builder 194 fmt.Fprintf(&buf, "[WARN] Provider %q produced an unexpected new value for %s, but we are tolerating it because it is using the legacy plugin SDK.\n The following problems may be the cause of any confusing errors from downstream operations:", n.ProviderAddr.ProviderConfig.Type, absAddr) 195 for _, err := range errs { 196 fmt.Fprintf(&buf, "\n - %s", tfdiags.FormatError(err)) 197 } 198 log.Print(buf.String()) 199 200 // The sort of inconsistency we won't catch here is if a known value 201 // in the plan is changed during apply. That can cause downstream 202 // problems because a dependent resource would make its own plan based 203 // on the planned value, and thus get a different result during the 204 // apply phase. This will usually lead to a "Provider produced invalid plan" 205 // error that incorrectly blames the downstream resource for the change. 206 207 } else { 208 for _, err := range errs { 209 diags = diags.Append(tfdiags.Sourceless( 210 tfdiags.Error, 211 "Provider produced inconsistent result after apply", 212 fmt.Sprintf( 213 "When applying changes to %s, provider %q produced an unexpected new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", 214 absAddr, n.ProviderAddr.ProviderConfig.Type, tfdiags.FormatError(err), 215 ), 216 )) 217 } 218 } 219 } 220 } 221 222 // If a provider returns a null or non-null object at the wrong time then 223 // we still want to save that but it often causes some confusing behaviors 224 // where it seems like Terraform is failing to take any action at all, 225 // so we'll generate some errors to draw attention to it. 226 if !diags.HasErrors() { 227 if change.Action == plans.Delete && !newVal.IsNull() { 228 diags = diags.Append(tfdiags.Sourceless( 229 tfdiags.Error, 230 "Provider returned invalid result object after apply", 231 fmt.Sprintf( 232 "After applying a %s plan, the provider returned a non-null object for %s. Destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository. Terraform will still save this errant object in the state for debugging and recovery.", 233 change.Action, n.Addr.Absolute(ctx.Path()), 234 ), 235 )) 236 } 237 if change.Action != plans.Delete && newVal.IsNull() { 238 diags = diags.Append(tfdiags.Sourceless( 239 tfdiags.Error, 240 "Provider returned invalid result object after apply", 241 fmt.Sprintf( 242 "After applying a %s plan, the provider returned a null object for %s. Only destroying should always produce a null value, so this is always a bug in the provider and should be reported in the provider's own repository.", 243 change.Action, n.Addr.Absolute(ctx.Path()), 244 ), 245 )) 246 } 247 } 248 249 // Sometimes providers return a null value when an operation fails for some 250 // reason, but we'd rather keep the prior state so that the error can be 251 // corrected on a subsequent run. We must only do this for null new value 252 // though, or else we may discard partial updates the provider was able to 253 // complete. 254 if diags.HasErrors() && newVal.IsNull() { 255 // Otherwise, we'll continue but using the prior state as the new value, 256 // making this effectively a no-op. If the item really _has_ been 257 // deleted then our next refresh will detect that and fix it up. 258 // If change.Action is Create then change.Before will also be null, 259 // which is fine. 260 newVal = change.Before 261 } 262 263 var newState *states.ResourceInstanceObject 264 if !newVal.IsNull() { // null value indicates that the object is deleted, so we won't set a new state in that case 265 newState = &states.ResourceInstanceObject{ 266 Status: states.ObjectReady, 267 Value: newVal, 268 Private: resp.Private, 269 Dependencies: n.Dependencies, // Should be populated by the caller from the StateDependencies method on the resource instance node 270 } 271 } 272 273 // Write the final state 274 if n.Output != nil { 275 *n.Output = newState 276 } 277 278 if diags.HasErrors() { 279 // If the caller provided an error pointer then they are expected to 280 // handle the error some other way and we treat our own result as 281 // success. 282 if n.Error != nil { 283 err := diags.Err() 284 *n.Error = err 285 log.Printf("[DEBUG] %s: apply errored, but we're indicating that via the Error pointer rather than returning it: %s", n.Addr.Absolute(ctx.Path()), err) 286 return nil, nil 287 } 288 } 289 290 return nil, diags.ErrWithWarnings() 291} 292 293// EvalApplyPre is an EvalNode implementation that does the pre-Apply work 294type EvalApplyPre struct { 295 Addr addrs.ResourceInstance 296 Gen states.Generation 297 State **states.ResourceInstanceObject 298 Change **plans.ResourceInstanceChange 299} 300 301// TODO: test 302func (n *EvalApplyPre) Eval(ctx EvalContext) (interface{}, error) { 303 change := *n.Change 304 absAddr := n.Addr.Absolute(ctx.Path()) 305 306 if change == nil { 307 panic(fmt.Sprintf("EvalApplyPre for %s called with nil Change", absAddr)) 308 } 309 310 if resourceHasUserVisibleApply(n.Addr) { 311 priorState := change.Before 312 plannedNewState := change.After 313 314 err := ctx.Hook(func(h Hook) (HookAction, error) { 315 return h.PreApply(absAddr, n.Gen, change.Action, priorState, plannedNewState) 316 }) 317 if err != nil { 318 return nil, err 319 } 320 } 321 322 return nil, nil 323} 324 325// EvalApplyPost is an EvalNode implementation that does the post-Apply work 326type EvalApplyPost struct { 327 Addr addrs.ResourceInstance 328 Gen states.Generation 329 State **states.ResourceInstanceObject 330 Error *error 331} 332 333// TODO: test 334func (n *EvalApplyPost) Eval(ctx EvalContext) (interface{}, error) { 335 state := *n.State 336 337 if resourceHasUserVisibleApply(n.Addr) { 338 absAddr := n.Addr.Absolute(ctx.Path()) 339 var newState cty.Value 340 if state != nil { 341 newState = state.Value 342 } else { 343 newState = cty.NullVal(cty.DynamicPseudoType) 344 } 345 var err error 346 if n.Error != nil { 347 err = *n.Error 348 } 349 350 hookErr := ctx.Hook(func(h Hook) (HookAction, error) { 351 return h.PostApply(absAddr, n.Gen, newState, err) 352 }) 353 if hookErr != nil { 354 return nil, hookErr 355 } 356 } 357 358 return nil, *n.Error 359} 360 361// EvalMaybeTainted is an EvalNode that takes the planned change, new value, 362// and possible error from an apply operation and produces a new instance 363// object marked as tainted if it appears that a create operation has failed. 364// 365// This EvalNode never returns an error, to ensure that a subsequent EvalNode 366// can still record the possibly-tainted object in the state. 367type EvalMaybeTainted struct { 368 Addr addrs.ResourceInstance 369 Gen states.Generation 370 Change **plans.ResourceInstanceChange 371 State **states.ResourceInstanceObject 372 Error *error 373 374 // If StateOutput is not nil, its referent will be assigned either the same 375 // pointer as State or a new object with its status set as Tainted, 376 // depending on whether an error is given and if this was a create action. 377 StateOutput **states.ResourceInstanceObject 378} 379 380// TODO: test 381func (n *EvalMaybeTainted) Eval(ctx EvalContext) (interface{}, error) { 382 state := *n.State 383 change := *n.Change 384 err := *n.Error 385 386 if state != nil && state.Status == states.ObjectTainted { 387 log.Printf("[TRACE] EvalMaybeTainted: %s was already tainted, so nothing to do", n.Addr.Absolute(ctx.Path())) 388 return nil, nil 389 } 390 391 if n.StateOutput != nil { 392 if err != nil && change.Action == plans.Create { 393 // If there are errors during a _create_ then the object is 394 // in an undefined state, and so we'll mark it as tainted so 395 // we can try again on the next run. 396 // 397 // We don't do this for other change actions because errors 398 // during updates will often not change the remote object at all. 399 // If there _were_ changes prior to the error, it's the provider's 400 // responsibility to record the effect of those changes in the 401 // object value it returned. 402 log.Printf("[TRACE] EvalMaybeTainted: %s encountered an error during creation, so it is now marked as tainted", n.Addr.Absolute(ctx.Path())) 403 *n.StateOutput = state.AsTainted() 404 } else { 405 *n.StateOutput = state 406 } 407 } 408 409 return nil, nil 410} 411 412// resourceHasUserVisibleApply returns true if the given resource is one where 413// apply actions should be exposed to the user. 414// 415// Certain resources do apply actions only as an implementation detail, so 416// these should not be advertised to code outside of this package. 417func resourceHasUserVisibleApply(addr addrs.ResourceInstance) bool { 418 // Only managed resources have user-visible apply actions. 419 // In particular, this excludes data resources since we "apply" these 420 // only as an implementation detail of removing them from state when 421 // they are destroyed. (When reading, they don't get here at all because 422 // we present them as "Refresh" actions.) 423 return addr.ContainingResource().Mode == addrs.ManagedResourceMode 424} 425 426// EvalApplyProvisioners is an EvalNode implementation that executes 427// the provisioners for a resource. 428// 429// TODO(mitchellh): This should probably be split up into a more fine-grained 430// ApplyProvisioner (single) that is looped over. 431type EvalApplyProvisioners struct { 432 Addr addrs.ResourceInstance 433 State **states.ResourceInstanceObject 434 ResourceConfig *configs.Resource 435 CreateNew *bool 436 Error *error 437 438 // When is the type of provisioner to run at this point 439 When configs.ProvisionerWhen 440} 441 442// TODO: test 443func (n *EvalApplyProvisioners) Eval(ctx EvalContext) (interface{}, error) { 444 absAddr := n.Addr.Absolute(ctx.Path()) 445 state := *n.State 446 if state == nil { 447 log.Printf("[TRACE] EvalApplyProvisioners: %s has no state, so skipping provisioners", n.Addr) 448 return nil, nil 449 } 450 if n.When == configs.ProvisionerWhenCreate && n.CreateNew != nil && !*n.CreateNew { 451 // If we're not creating a new resource, then don't run provisioners 452 log.Printf("[TRACE] EvalApplyProvisioners: %s is not freshly-created, so no provisioning is required", n.Addr) 453 return nil, nil 454 } 455 if state.Status == states.ObjectTainted { 456 // No point in provisioning an object that is already tainted, since 457 // it's going to get recreated on the next apply anyway. 458 log.Printf("[TRACE] EvalApplyProvisioners: %s is tainted, so skipping provisioning", n.Addr) 459 return nil, nil 460 } 461 462 provs := n.filterProvisioners() 463 if len(provs) == 0 { 464 // We have no provisioners, so don't do anything 465 return nil, nil 466 } 467 468 if n.Error != nil && *n.Error != nil { 469 // We're already tainted, so just return out 470 return nil, nil 471 } 472 473 { 474 // Call pre hook 475 err := ctx.Hook(func(h Hook) (HookAction, error) { 476 return h.PreProvisionInstance(absAddr, state.Value) 477 }) 478 if err != nil { 479 return nil, err 480 } 481 } 482 483 // If there are no errors, then we append it to our output error 484 // if we have one, otherwise we just output it. 485 err := n.apply(ctx, provs) 486 if err != nil { 487 *n.Error = multierror.Append(*n.Error, err) 488 if n.Error == nil { 489 return nil, err 490 } else { 491 log.Printf("[TRACE] EvalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", absAddr) 492 return nil, nil 493 } 494 } 495 496 { 497 // Call post hook 498 err := ctx.Hook(func(h Hook) (HookAction, error) { 499 return h.PostProvisionInstance(absAddr, state.Value) 500 }) 501 if err != nil { 502 return nil, err 503 } 504 } 505 506 return nil, nil 507} 508 509// filterProvisioners filters the provisioners on the resource to only 510// the provisioners specified by the "when" option. 511func (n *EvalApplyProvisioners) filterProvisioners() []*configs.Provisioner { 512 // Fast path the zero case 513 if n.ResourceConfig == nil || n.ResourceConfig.Managed == nil { 514 return nil 515 } 516 517 if len(n.ResourceConfig.Managed.Provisioners) == 0 { 518 return nil 519 } 520 521 result := make([]*configs.Provisioner, 0, len(n.ResourceConfig.Managed.Provisioners)) 522 for _, p := range n.ResourceConfig.Managed.Provisioners { 523 if p.When == n.When { 524 result = append(result, p) 525 } 526 } 527 528 return result 529} 530 531func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisioner) error { 532 var diags tfdiags.Diagnostics 533 instanceAddr := n.Addr 534 absAddr := instanceAddr.Absolute(ctx.Path()) 535 536 // If there's a connection block defined directly inside the resource block 537 // then it'll serve as a base connection configuration for all of the 538 // provisioners. 539 var baseConn hcl.Body 540 if n.ResourceConfig.Managed != nil && n.ResourceConfig.Managed.Connection != nil { 541 baseConn = n.ResourceConfig.Managed.Connection.Config 542 } 543 544 for _, prov := range provs { 545 log.Printf("[TRACE] EvalApplyProvisioners: provisioning %s with %q", absAddr, prov.Type) 546 547 // Get the provisioner 548 provisioner := ctx.Provisioner(prov.Type) 549 schema := ctx.ProvisionerSchema(prov.Type) 550 551 keyData := EvalDataForInstanceKey(instanceAddr.Key) 552 553 // Evaluate the main provisioner configuration. 554 config, _, configDiags := ctx.EvaluateBlock(prov.Config, schema, instanceAddr, keyData) 555 diags = diags.Append(configDiags) 556 557 // If the provisioner block contains a connection block of its own then 558 // it can override the base connection configuration, if any. 559 var localConn hcl.Body 560 if prov.Connection != nil { 561 localConn = prov.Connection.Config 562 } 563 564 var connBody hcl.Body 565 switch { 566 case baseConn != nil && localConn != nil: 567 // Our standard merging logic applies here, similar to what we do 568 // with _override.tf configuration files: arguments from the 569 // base connection block will be masked by any arguments of the 570 // same name in the local connection block. 571 connBody = configs.MergeBodies(baseConn, localConn) 572 case baseConn != nil: 573 connBody = baseConn 574 case localConn != nil: 575 connBody = localConn 576 } 577 578 // start with an empty connInfo 579 connInfo := cty.NullVal(connectionBlockSupersetSchema.ImpliedType()) 580 581 if connBody != nil { 582 var connInfoDiags tfdiags.Diagnostics 583 connInfo, _, connInfoDiags = ctx.EvaluateBlock(connBody, connectionBlockSupersetSchema, instanceAddr, keyData) 584 diags = diags.Append(connInfoDiags) 585 if diags.HasErrors() { 586 // "on failure continue" setting only applies to failures of the 587 // provisioner itself, not to invalid configuration. 588 return diags.Err() 589 } 590 } 591 592 { 593 // Call pre hook 594 err := ctx.Hook(func(h Hook) (HookAction, error) { 595 return h.PreProvisionInstanceStep(absAddr, prov.Type) 596 }) 597 if err != nil { 598 return err 599 } 600 } 601 602 // The output function 603 outputFn := func(msg string) { 604 ctx.Hook(func(h Hook) (HookAction, error) { 605 h.ProvisionOutput(absAddr, prov.Type, msg) 606 return HookActionContinue, nil 607 }) 608 } 609 610 output := CallbackUIOutput{OutputFn: outputFn} 611 resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{ 612 Config: config, 613 Connection: connInfo, 614 UIOutput: &output, 615 }) 616 applyDiags := resp.Diagnostics.InConfigBody(prov.Config) 617 618 // Call post hook 619 hookErr := ctx.Hook(func(h Hook) (HookAction, error) { 620 return h.PostProvisionInstanceStep(absAddr, prov.Type, applyDiags.Err()) 621 }) 622 623 switch prov.OnFailure { 624 case configs.ProvisionerOnFailureContinue: 625 if applyDiags.HasErrors() { 626 log.Printf("[WARN] Errors while provisioning %s with %q, but continuing as requested in configuration", n.Addr, prov.Type) 627 } else { 628 // Maybe there are warnings that we still want to see 629 diags = diags.Append(applyDiags) 630 } 631 default: 632 diags = diags.Append(applyDiags) 633 if applyDiags.HasErrors() { 634 log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type) 635 return diags.Err() 636 } 637 } 638 639 // Deal with the hook 640 if hookErr != nil { 641 return hookErr 642 } 643 } 644 645 return diags.ErrWithWarnings() 646} 647