github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/deploy/step.go (about) 1 // Copyright 2016-2021, Pulumi Corporation. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package deploy 16 17 import ( 18 "errors" 19 "fmt" 20 "strings" 21 22 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" 23 "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 24 "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" 25 "github.com/pulumi/pulumi/sdk/v3/go/common/display" 26 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 27 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 28 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 29 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 30 "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" 31 ) 32 33 // StepCompleteFunc is the type of functions returned from Step.Apply. These functions are to be called 34 // when the engine has fully retired a step. 35 type StepCompleteFunc func() 36 37 // Step is a specification for a deployment operation. 38 type Step interface { 39 // Apply applies or previews this step. It returns the status of the resource after the step application, 40 // a function to call to signal that this step has fully completed, and an error, if one occurred while applying 41 // the step. 42 // 43 // The returned StepCompleteFunc, if not nil, must be called after committing the results of this step into 44 // the state of the deployment. 45 Apply(preview bool) (resource.Status, StepCompleteFunc, error) // applies or previews this step. 46 47 Op() display.StepOp // the operation performed by this step. 48 URN() resource.URN // the resource URN (for before and after). 49 Type() tokens.Type // the type affected by this step. 50 Provider() string // the provider reference for this step. 51 Old() *resource.State // the state of the resource before performing this step. 52 New() *resource.State // the state of the resource after performing this step. 53 Res() *resource.State // the latest state for the resource that is known (worst case, old). 54 Logical() bool // true if this step represents a logical operation in the program. 55 Deployment() *Deployment // the owning deployment. 56 } 57 58 // SameStep is a mutating step that does nothing. 59 type SameStep struct { 60 deployment *Deployment // the current deployment. 61 reg RegisterResourceEvent // the registration intent to convey a URN back to. 62 old *resource.State // the state of the resource before this step. 63 new *resource.State // the state of the resource after this step. 64 65 // If this is a same-step for a resource being created but which was not --target'ed by the user 66 // (and thus was skipped). 67 skippedCreate bool 68 } 69 70 var _ Step = (*SameStep)(nil) 71 72 func NewSameStep(deployment *Deployment, reg RegisterResourceEvent, old, new *resource.State) Step { 73 contract.Assert(old != nil) 74 contract.Assert(old.URN != "") 75 contract.Assert(old.ID != "" || !old.Custom) 76 contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type)) 77 contract.Assert(!old.Delete) 78 contract.Assert(new != nil) 79 contract.Assert(new.URN != "") 80 contract.Assert(new.ID == "") 81 contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type)) 82 contract.Assert(!new.Delete) 83 return &SameStep{ 84 deployment: deployment, 85 reg: reg, 86 old: old, 87 new: new, 88 } 89 } 90 91 // NewSkippedCreateStep produces a SameStep for a resource that was created but not targeted 92 // by the user (and thus was skipped). These act as no-op steps (hence 'same') since we are not 93 // actually creating the resource, but ensure that we complete resource-registration and convey the 94 // right information downstream. For example, we will not write these into the checkpoint file. 95 func NewSkippedCreateStep(deployment *Deployment, reg RegisterResourceEvent, new *resource.State) Step { 96 contract.Assert(new != nil) 97 contract.Assert(new.URN != "") 98 contract.Assert(new.ID == "") 99 contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type)) 100 contract.Assert(!new.Delete) 101 102 // Make the old state here a direct copy of the new state 103 old := *new 104 return &SameStep{ 105 deployment: deployment, 106 reg: reg, 107 old: &old, 108 new: new, 109 skippedCreate: true, 110 } 111 } 112 113 func (s *SameStep) Op() display.StepOp { return OpSame } 114 func (s *SameStep) Deployment() *Deployment { return s.deployment } 115 func (s *SameStep) Type() tokens.Type { return s.new.Type } 116 func (s *SameStep) Provider() string { return s.new.Provider } 117 func (s *SameStep) URN() resource.URN { return s.new.URN } 118 func (s *SameStep) Old() *resource.State { return s.old } 119 func (s *SameStep) New() *resource.State { return s.new } 120 func (s *SameStep) Res() *resource.State { return s.new } 121 func (s *SameStep) Logical() bool { return true } 122 123 func (s *SameStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 124 // Retain the ID and outputs 125 s.new.ID = s.old.ID 126 s.new.Outputs = s.old.Outputs 127 128 // If the resource is a provider, ensure that it is present in the registry under the appropriate URNs. 129 if providers.IsProviderType(s.new.Type) { 130 ref, err := providers.NewReference(s.new.URN, s.new.ID) 131 if err != nil { 132 return resource.StatusOK, nil, 133 fmt.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) 134 } 135 if s.Deployment() != nil { 136 s.Deployment().SameProvider(ref) 137 } 138 } 139 140 complete := func() { s.reg.Done(&RegisterResult{State: s.new}) } 141 return resource.StatusOK, complete, nil 142 } 143 144 func (s *SameStep) IsSkippedCreate() bool { 145 return s.skippedCreate 146 } 147 148 // CreateStep is a mutating step that creates an entirely new resource. 149 type CreateStep struct { 150 deployment *Deployment // the current deployment. 151 reg RegisterResourceEvent // the registration intent to convey a URN back to. 152 old *resource.State // the state of the existing resource (only for replacements). 153 new *resource.State // the state of the resource after this step. 154 keys []resource.PropertyKey // the keys causing replacement (only for replacements). 155 diffs []resource.PropertyKey // the keys causing a diff (only for replacements). 156 detailedDiff map[string]plugin.PropertyDiff // the structured property diff (only for replacements). 157 replacing bool // true if this is a create due to a replacement. 158 pendingDelete bool // true if this replacement should create a pending delete. 159 } 160 161 var _ Step = (*CreateStep)(nil) 162 163 func NewCreateStep(deployment *Deployment, reg RegisterResourceEvent, new *resource.State) Step { 164 contract.Assert(reg != nil) 165 contract.Assert(new != nil) 166 contract.Assert(new.URN != "") 167 contract.Assert(new.ID == "") 168 contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type)) 169 contract.Assert(!new.Delete) 170 contract.Assert(!new.External) 171 return &CreateStep{ 172 deployment: deployment, 173 reg: reg, 174 new: new, 175 } 176 } 177 178 func NewCreateReplacementStep(deployment *Deployment, reg RegisterResourceEvent, old, new *resource.State, 179 keys, diffs []resource.PropertyKey, detailedDiff map[string]plugin.PropertyDiff, pendingDelete bool) Step { 180 181 contract.Assert(reg != nil) 182 contract.Assert(old != nil) 183 contract.Assert(old.URN != "") 184 contract.Assert(old.ID != "" || !old.Custom) 185 contract.Assert(!old.Delete) 186 contract.Assert(new != nil) 187 contract.Assert(new.URN != "") 188 contract.Assert(new.ID == "") 189 contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type)) 190 contract.Assert(!new.Delete) 191 contract.Assert(!new.External) 192 return &CreateStep{ 193 deployment: deployment, 194 reg: reg, 195 old: old, 196 new: new, 197 keys: keys, 198 diffs: diffs, 199 detailedDiff: detailedDiff, 200 replacing: true, 201 pendingDelete: pendingDelete, 202 } 203 } 204 205 func (s *CreateStep) Op() display.StepOp { 206 if s.replacing { 207 return OpCreateReplacement 208 } 209 return OpCreate 210 } 211 func (s *CreateStep) Deployment() *Deployment { return s.deployment } 212 func (s *CreateStep) Type() tokens.Type { return s.new.Type } 213 func (s *CreateStep) Provider() string { return s.new.Provider } 214 func (s *CreateStep) URN() resource.URN { return s.new.URN } 215 func (s *CreateStep) Old() *resource.State { return s.old } 216 func (s *CreateStep) New() *resource.State { return s.new } 217 func (s *CreateStep) Res() *resource.State { return s.new } 218 func (s *CreateStep) Keys() []resource.PropertyKey { return s.keys } 219 func (s *CreateStep) Diffs() []resource.PropertyKey { return s.diffs } 220 func (s *CreateStep) DetailedDiff() map[string]plugin.PropertyDiff { return s.detailedDiff } 221 func (s *CreateStep) Logical() bool { return !s.replacing } 222 223 func (s *CreateStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 224 var resourceError error 225 resourceStatus := resource.StatusOK 226 if s.new.Custom { 227 // Invoke the Create RPC function for this provider: 228 prov, err := getProvider(s) 229 if err != nil { 230 return resource.StatusOK, nil, err 231 } 232 233 id, outs, rst, err := prov.Create(s.URN(), s.new.Inputs, s.new.CustomTimeouts.Create, s.deployment.preview) 234 if err != nil { 235 if rst != resource.StatusPartialFailure { 236 return rst, nil, err 237 } 238 239 resourceError = err 240 resourceStatus = rst 241 242 if initErr, isInitErr := err.(*plugin.InitError); isInitErr { 243 s.new.InitErrors = initErr.Reasons 244 } 245 } 246 247 if !preview && id == "" { 248 return resourceStatus, nil, fmt.Errorf("provider did not return an ID from Create") 249 } 250 251 // Copy any of the default and output properties on the live object state. 252 s.new.ID = id 253 s.new.Outputs = outs 254 } 255 256 // Mark the old resource as pending deletion if necessary. 257 if s.replacing && s.pendingDelete { 258 s.old.Delete = true 259 } 260 261 complete := func() { s.reg.Done(&RegisterResult{State: s.new}) } 262 if resourceError == nil { 263 return resourceStatus, complete, nil 264 } 265 return resourceStatus, complete, resourceError 266 } 267 268 // DeleteStep is a mutating step that deletes an existing resource. If `old` is marked "External", 269 // DeleteStep is a no-op. 270 type DeleteStep struct { 271 deployment *Deployment // the current deployment. 272 old *resource.State // the state of the existing resource. 273 replacing bool // true if part of a replacement. 274 otherDeletions map[resource.URN]bool // other resources that are planned to delete 275 } 276 277 var _ Step = (*DeleteStep)(nil) 278 279 func NewDeleteStep(deployment *Deployment, otherDeletions map[resource.URN]bool, old *resource.State) Step { 280 contract.Assert(old != nil) 281 contract.Assert(old.URN != "") 282 contract.Assert(old.ID != "" || !old.Custom) 283 contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type)) 284 contract.Assert(otherDeletions != nil) 285 return &DeleteStep{ 286 deployment: deployment, 287 old: old, 288 otherDeletions: otherDeletions, 289 } 290 } 291 292 func NewDeleteReplacementStep( 293 deployment *Deployment, 294 otherDeletions map[resource.URN]bool, 295 old *resource.State, 296 pendingReplace bool, 297 ) Step { 298 contract.Assert(old != nil) 299 contract.Assert(old.URN != "") 300 contract.Assert(old.ID != "" || !old.Custom) 301 contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type)) 302 contract.Assert(otherDeletions != nil) 303 304 // There are two cases in which we create a delete-replacment step: 305 // 306 // 1. When creating the delete steps that occur due to a delete-before-replace 307 // 2. When creating the delete step that occurs due to a delete-after-replace 308 // 309 // In the former case, the persistence layer may require that the resource remain in the 310 // checkpoint file for purposes of checkpoint integrity. We communicate this case by means 311 // of the `PendingReplacement` field on `resource.State`, which we set here. 312 // 313 // In the latter case, the resource must be deleted, but the deletion may not occur if an earlier step fails. 314 // The engine requires that the fact that the old resource must be deleted is persisted in the checkpoint so 315 // that it can issue a deletion of this resource on the next update to this stack. 316 contract.Assert(pendingReplace != old.Delete) 317 old.PendingReplacement = pendingReplace 318 return &DeleteStep{ 319 deployment: deployment, 320 otherDeletions: otherDeletions, 321 old: old, 322 replacing: true, 323 } 324 } 325 326 func (s *DeleteStep) Op() display.StepOp { 327 if s.old.External { 328 if s.replacing { 329 return OpDiscardReplaced 330 } 331 return OpReadDiscard 332 } 333 334 if s.replacing { 335 return OpDeleteReplaced 336 } 337 return OpDelete 338 } 339 func (s *DeleteStep) Deployment() *Deployment { return s.deployment } 340 func (s *DeleteStep) Type() tokens.Type { return s.old.Type } 341 func (s *DeleteStep) Provider() string { return s.old.Provider } 342 func (s *DeleteStep) URN() resource.URN { return s.old.URN } 343 func (s *DeleteStep) Old() *resource.State { return s.old } 344 func (s *DeleteStep) New() *resource.State { return nil } 345 func (s *DeleteStep) Res() *resource.State { return s.old } 346 func (s *DeleteStep) Logical() bool { return !s.replacing } 347 348 func isDeletedWith(with resource.URN, otherDeletions map[resource.URN]bool) bool { 349 if with == "" { 350 return false 351 } 352 r, ok := otherDeletions[with] 353 if !ok { 354 return false 355 } 356 return r 357 } 358 359 func (s *DeleteStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 360 // Refuse to delete protected resources (unless we're replacing them in 361 // which case we will of checked protect elsewhere) 362 if !s.replacing && s.old.Protect { 363 return resource.StatusOK, nil, fmt.Errorf("unable to delete resource %q\n"+ 364 "as it is currently marked for protection. To unprotect the resource, "+ 365 "either remove the `protect` flag from the resource in your Pulumi "+ 366 "program and run `pulumi up` or use the command:\n"+ 367 "`pulumi state unprotect '%s'`", s.old.URN, s.old.URN) 368 } 369 370 if preview { 371 // Do nothing in preview 372 } else if s.old.External { 373 // Deleting an External resource is a no-op, since Pulumi does not own the lifecycle. 374 } else if s.old.RetainOnDelete { 375 // Deleting a "drop on delete" is a no-op as the user has explicitly asked us to not delete the resource. 376 } else if isDeletedWith(s.old.DeletedWith, s.otherDeletions) { 377 // No need to delete this resource since this resource will be deleted by the another deletion 378 } else if s.old.Custom { 379 // Not preview and not external and not Drop and is custom, do the actual delete 380 381 // Invoke the Delete RPC function for this provider: 382 prov, err := getProvider(s) 383 if err != nil { 384 return resource.StatusOK, nil, err 385 } 386 387 if rst, err := prov.Delete(s.URN(), s.old.ID, s.old.Outputs, s.old.CustomTimeouts.Delete); err != nil { 388 return rst, nil, err 389 } 390 } 391 392 return resource.StatusOK, func() {}, nil 393 } 394 395 type RemovePendingReplaceStep struct { 396 deployment *Deployment // the current deployment. 397 old *resource.State // the state of the existing resource. 398 } 399 400 func NewRemovePendingReplaceStep(deployment *Deployment, old *resource.State) Step { 401 contract.Assert(old != nil) 402 contract.Assert(old.PendingReplacement) 403 return &RemovePendingReplaceStep{ 404 deployment: deployment, 405 old: old, 406 } 407 } 408 409 func (s *RemovePendingReplaceStep) Op() display.StepOp { 410 return OpRemovePendingReplace 411 } 412 func (s *RemovePendingReplaceStep) Deployment() *Deployment { return s.deployment } 413 func (s *RemovePendingReplaceStep) Type() tokens.Type { return s.old.Type } 414 func (s *RemovePendingReplaceStep) Provider() string { return s.old.Provider } 415 func (s *RemovePendingReplaceStep) URN() resource.URN { return s.old.URN } 416 func (s *RemovePendingReplaceStep) Old() *resource.State { return s.old } 417 func (s *RemovePendingReplaceStep) New() *resource.State { return nil } 418 func (s *RemovePendingReplaceStep) Res() *resource.State { return s.old } 419 func (s *RemovePendingReplaceStep) Logical() bool { return false } 420 421 func (s *RemovePendingReplaceStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 422 return resource.StatusOK, nil, nil 423 } 424 425 // UpdateStep is a mutating step that updates an existing resource's state. 426 type UpdateStep struct { 427 deployment *Deployment // the current deployment. 428 reg RegisterResourceEvent // the registration intent to convey a URN back to. 429 old *resource.State // the state of the existing resource. 430 new *resource.State // the newly computed state of the resource after updating. 431 stables []resource.PropertyKey // an optional list of properties that won't change during this update. 432 diffs []resource.PropertyKey // the keys causing a diff. 433 detailedDiff map[string]plugin.PropertyDiff // the structured diff. 434 ignoreChanges []string // a list of property paths to ignore when updating. 435 } 436 437 var _ Step = (*UpdateStep)(nil) 438 439 func NewUpdateStep(deployment *Deployment, reg RegisterResourceEvent, old, new *resource.State, 440 stables, diffs []resource.PropertyKey, detailedDiff map[string]plugin.PropertyDiff, 441 442 ignoreChanges []string) Step { 443 contract.Assert(old != nil) 444 contract.Assert(old.URN != "") 445 contract.Assert(old.ID != "" || !old.Custom) 446 contract.Assert(!old.Custom || old.Provider != "" || providers.IsProviderType(old.Type)) 447 contract.Assert(!old.Delete) 448 contract.Assert(new != nil) 449 contract.Assert(new.URN != "") 450 contract.Assert(new.ID == "") 451 contract.Assert(!new.Custom || new.Provider != "" || providers.IsProviderType(new.Type)) 452 contract.Assert(!new.Delete) 453 contract.Assert(!new.External) 454 contract.Assert(!old.External) 455 return &UpdateStep{ 456 deployment: deployment, 457 reg: reg, 458 old: old, 459 new: new, 460 stables: stables, 461 diffs: diffs, 462 detailedDiff: detailedDiff, 463 ignoreChanges: ignoreChanges, 464 } 465 } 466 467 func (s *UpdateStep) Op() display.StepOp { return OpUpdate } 468 func (s *UpdateStep) Deployment() *Deployment { return s.deployment } 469 func (s *UpdateStep) Type() tokens.Type { return s.new.Type } 470 func (s *UpdateStep) Provider() string { return s.new.Provider } 471 func (s *UpdateStep) URN() resource.URN { return s.new.URN } 472 func (s *UpdateStep) Old() *resource.State { return s.old } 473 func (s *UpdateStep) New() *resource.State { return s.new } 474 func (s *UpdateStep) Res() *resource.State { return s.new } 475 func (s *UpdateStep) Logical() bool { return true } 476 func (s *UpdateStep) Diffs() []resource.PropertyKey { return s.diffs } 477 func (s *UpdateStep) DetailedDiff() map[string]plugin.PropertyDiff { return s.detailedDiff } 478 479 func (s *UpdateStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 480 // Always propagate the ID, even in previews and refreshes. 481 s.new.ID = s.old.ID 482 483 var resourceError error 484 resourceStatus := resource.StatusOK 485 if s.new.Custom { 486 // Invoke the Update RPC function for this provider: 487 prov, err := getProvider(s) 488 if err != nil { 489 return resource.StatusOK, nil, err 490 } 491 492 // Update to the combination of the old "all" state, but overwritten with new inputs. 493 outs, rst, upderr := prov.Update(s.URN(), s.old.ID, s.old.Outputs, s.new.Inputs, 494 s.new.CustomTimeouts.Update, s.ignoreChanges, s.deployment.preview) 495 if upderr != nil { 496 if rst != resource.StatusPartialFailure { 497 return rst, nil, upderr 498 } 499 500 resourceError = upderr 501 resourceStatus = rst 502 503 if initErr, isInitErr := upderr.(*plugin.InitError); isInitErr { 504 s.new.InitErrors = initErr.Reasons 505 } 506 } 507 508 // Now copy any output state back in case the update triggered cascading updates to other properties. 509 s.new.Outputs = outs 510 } 511 512 // Finally, mark this operation as complete. 513 complete := func() { s.reg.Done(&RegisterResult{State: s.new}) } 514 if resourceError == nil { 515 return resourceStatus, complete, nil 516 } 517 return resourceStatus, complete, resourceError 518 } 519 520 // ReplaceStep is a logical step indicating a resource will be replaced. This is comprised of three physical steps: 521 // a creation of the new resource, any number of intervening updates of dependents to the new resource, and then 522 // a deletion of the now-replaced old resource. This logical step is primarily here for tools and visualization. 523 type ReplaceStep struct { 524 deployment *Deployment // the current deployment. 525 old *resource.State // the state of the existing resource. 526 new *resource.State // the new state snapshot. 527 keys []resource.PropertyKey // the keys causing replacement. 528 diffs []resource.PropertyKey // the keys causing a diff. 529 detailedDiff map[string]plugin.PropertyDiff // the structured property diff. 530 pendingDelete bool // true if a pending deletion should happen. 531 } 532 533 var _ Step = (*ReplaceStep)(nil) 534 535 func NewReplaceStep(deployment *Deployment, old, new *resource.State, keys, diffs []resource.PropertyKey, 536 detailedDiff map[string]plugin.PropertyDiff, pendingDelete bool) Step { 537 538 contract.Assert(old != nil) 539 contract.Assert(old.URN != "") 540 contract.Assert(old.ID != "" || !old.Custom) 541 contract.Assert(!old.Delete) 542 contract.Assert(new != nil) 543 contract.Assert(new.URN != "") 544 // contract.Assert(new.ID == "") 545 contract.Assert(!new.Delete) 546 return &ReplaceStep{ 547 deployment: deployment, 548 old: old, 549 new: new, 550 keys: keys, 551 diffs: diffs, 552 detailedDiff: detailedDiff, 553 pendingDelete: pendingDelete, 554 } 555 } 556 557 func (s *ReplaceStep) Op() display.StepOp { return OpReplace } 558 func (s *ReplaceStep) Deployment() *Deployment { return s.deployment } 559 func (s *ReplaceStep) Type() tokens.Type { return s.new.Type } 560 func (s *ReplaceStep) Provider() string { return s.new.Provider } 561 func (s *ReplaceStep) URN() resource.URN { return s.new.URN } 562 func (s *ReplaceStep) Old() *resource.State { return s.old } 563 func (s *ReplaceStep) New() *resource.State { return s.new } 564 func (s *ReplaceStep) Res() *resource.State { return s.new } 565 func (s *ReplaceStep) Keys() []resource.PropertyKey { return s.keys } 566 func (s *ReplaceStep) Diffs() []resource.PropertyKey { return s.diffs } 567 func (s *ReplaceStep) DetailedDiff() map[string]plugin.PropertyDiff { return s.detailedDiff } 568 func (s *ReplaceStep) Logical() bool { return true } 569 570 func (s *ReplaceStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 571 // If this is a pending delete, we should have marked the old resource for deletion in the CreateReplacement step. 572 contract.Assert(!s.pendingDelete || s.old.Delete) 573 return resource.StatusOK, func() {}, nil 574 } 575 576 // ReadStep is a step indicating that an existing resources will be "read" and projected into the Pulumi object 577 // model. Resources that are read are marked with the "External" bit which indicates to the engine that it does 578 // not own this resource's lifeycle. 579 // 580 // A resource with a given URN can transition freely between an "external" state and a non-external state. If 581 // a URN that was previously marked "External" (i.e. was the target of a ReadStep in a previous deployment) is the 582 // target of a RegisterResource in the next deployment, a CreateReplacement step will be issued to indicate the 583 // transition from external to owned. If a URN that was previously not marked "External" is the target of a 584 // ReadResource in the next deployment, a ReadReplacement step will be issued to indicate the transition from owned to 585 // external. 586 type ReadStep struct { 587 deployment *Deployment // the deployment that produced this read 588 event ReadResourceEvent // the event that should be signaled upon completion 589 old *resource.State // the old resource state, if one exists for this urn 590 new *resource.State // the new resource state, to be used to query the provider 591 replacing bool // whether or not the new resource is replacing the old resource 592 } 593 594 // NewReadStep creates a new Read step. 595 func NewReadStep(deployment *Deployment, event ReadResourceEvent, old, new *resource.State) Step { 596 contract.Assert(new != nil) 597 contract.Assertf(new.URN != "", "Read URN was empty") 598 contract.Assertf(new.ID != "", "Read ID was empty") 599 contract.Assertf(new.External, "target of Read step must be marked External") 600 contract.Assertf(new.Custom, "target of Read step must be Custom") 601 602 // If Old was given, it's either an external resource or its ID is equal to the 603 // ID that we are preparing to read. 604 if old != nil { 605 contract.Assert(old.ID == new.ID || old.External) 606 } 607 608 return &ReadStep{ 609 deployment: deployment, 610 event: event, 611 old: old, 612 new: new, 613 replacing: false, 614 } 615 } 616 617 // NewReadReplacementStep creates a new Read step with the `replacing` flag set. When executed, 618 // it will pend deletion of the "old" resource, which must not be an external resource. 619 func NewReadReplacementStep(deployment *Deployment, event ReadResourceEvent, old, new *resource.State) Step { 620 contract.Assert(new != nil) 621 contract.Assertf(new.URN != "", "Read URN was empty") 622 contract.Assertf(new.ID != "", "Read ID was empty") 623 contract.Assertf(new.External, "target of ReadReplacement step must be marked External") 624 contract.Assertf(new.Custom, "target of ReadReplacement step must be Custom") 625 contract.Assert(old != nil) 626 contract.Assertf(!old.External, "old target of ReadReplacement step must not be External") 627 return &ReadStep{ 628 deployment: deployment, 629 event: event, 630 old: old, 631 new: new, 632 replacing: true, 633 } 634 } 635 636 func (s *ReadStep) Op() display.StepOp { 637 if s.replacing { 638 return OpReadReplacement 639 } 640 641 return OpRead 642 } 643 644 func (s *ReadStep) Deployment() *Deployment { return s.deployment } 645 func (s *ReadStep) Type() tokens.Type { return s.new.Type } 646 func (s *ReadStep) Provider() string { return s.new.Provider } 647 func (s *ReadStep) URN() resource.URN { return s.new.URN } 648 func (s *ReadStep) Old() *resource.State { return s.old } 649 func (s *ReadStep) New() *resource.State { return s.new } 650 func (s *ReadStep) Res() *resource.State { return s.new } 651 func (s *ReadStep) Logical() bool { return !s.replacing } 652 653 func (s *ReadStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 654 urn := s.new.URN 655 id := s.new.ID 656 657 var resourceError error 658 resourceStatus := resource.StatusOK 659 // Unlike most steps, Read steps run during previews. The only time 660 // we can't run is if the ID we are given is unknown. 661 if id == plugin.UnknownStringValue { 662 s.new.Outputs = resource.PropertyMap{} 663 } else { 664 prov, err := getProvider(s) 665 if err != nil { 666 return resource.StatusOK, nil, err 667 } 668 669 result, rst, err := prov.Read(urn, id, nil, s.new.Inputs) 670 if err != nil { 671 if rst != resource.StatusPartialFailure { 672 return rst, nil, err 673 } 674 675 resourceError = err 676 resourceStatus = rst 677 678 if initErr, isInitErr := err.(*plugin.InitError); isInitErr { 679 s.new.InitErrors = initErr.Reasons 680 } 681 } 682 683 // If there is no such resource, return an error indicating as such. 684 if result.Outputs == nil { 685 return resource.StatusOK, nil, fmt.Errorf("resource '%s' does not exist", id) 686 } 687 s.new.Outputs = result.Outputs 688 689 if result.ID != "" { 690 s.new.ID = result.ID 691 } 692 } 693 694 // If we were asked to replace an existing, non-External resource, pend the 695 // deletion here. 696 if s.replacing { 697 s.old.Delete = true 698 } 699 700 complete := func() { s.event.Done(&ReadResult{State: s.new}) } 701 if resourceError == nil { 702 return resourceStatus, complete, nil 703 } 704 return resourceStatus, complete, resourceError 705 } 706 707 // RefreshStep is a step used to track the progress of a refresh operation. A refresh operation updates the an existing 708 // resource by reading its current state from its provider plugin. These steps are not issued by the step generator; 709 // instead, they are issued by the deployment executor as the optional first step in deployment execution. 710 type RefreshStep struct { 711 deployment *Deployment // the deployment that produced this refresh 712 old *resource.State // the old resource state, if one exists for this urn 713 new *resource.State // the new resource state, to be used to query the provider 714 done chan<- bool // the channel to use to signal completion, if any 715 } 716 717 // NewRefreshStep creates a new Refresh step. 718 func NewRefreshStep(deployment *Deployment, old *resource.State, done chan<- bool) Step { 719 contract.Assert(old != nil) 720 721 // NOTE: we set the new state to the old state by default so that we don't interpret step failures as deletes. 722 return &RefreshStep{ 723 deployment: deployment, 724 old: old, 725 new: old, 726 done: done, 727 } 728 } 729 730 func (s *RefreshStep) Op() display.StepOp { return OpRefresh } 731 func (s *RefreshStep) Deployment() *Deployment { return s.deployment } 732 func (s *RefreshStep) Type() tokens.Type { return s.old.Type } 733 func (s *RefreshStep) Provider() string { return s.old.Provider } 734 func (s *RefreshStep) URN() resource.URN { return s.old.URN } 735 func (s *RefreshStep) Old() *resource.State { return s.old } 736 func (s *RefreshStep) New() *resource.State { return s.new } 737 func (s *RefreshStep) Res() *resource.State { return s.old } 738 func (s *RefreshStep) Logical() bool { return false } 739 740 // ResultOp returns the operation that corresponds to the change to this resource after reading its current state, if 741 // any. 742 func (s *RefreshStep) ResultOp() display.StepOp { 743 if s.new == nil { 744 return OpDelete 745 } 746 if s.new == s.old || s.old.Outputs.Diff(s.new.Outputs) == nil { 747 return OpSame 748 } 749 return OpUpdate 750 } 751 752 func (s *RefreshStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 753 var complete func() 754 if s.done != nil { 755 complete = func() { close(s.done) } 756 } 757 758 resourceID := s.old.ID 759 760 // Component, provider, and pending-replace resources never change with a refresh; just return the current state. 761 if !s.old.Custom || providers.IsProviderType(s.old.Type) || s.old.PendingReplacement { 762 return resource.StatusOK, complete, nil 763 } 764 765 // For a custom resource, fetch the resource's provider and read the resource's current state. 766 prov, err := getProvider(s) 767 if err != nil { 768 return resource.StatusOK, nil, err 769 } 770 771 var initErrors []string 772 refreshed, rst, err := prov.Read(s.old.URN, resourceID, s.old.Inputs, s.old.Outputs) 773 if err != nil { 774 if rst != resource.StatusPartialFailure { 775 return rst, nil, err 776 } 777 if initErr, isInitErr := err.(*plugin.InitError); isInitErr { 778 initErrors = initErr.Reasons 779 780 // Partial failure SHOULD NOT cause refresh to fail. Instead: 781 // 782 // 1. Warn instead that during refresh we noticed the resource has become unhealthy. 783 // 2. Make sure the initialization errors are persisted in the state, so that the next 784 // `pulumi up` will surface them to the user. 785 err = nil 786 msg := fmt.Sprintf("Refreshed resource is in an unhealthy state:\n* %s", strings.Join(initErrors, "\n* ")) 787 s.Deployment().Diag().Warningf(diag.RawMessage(s.URN(), msg)) 788 } 789 } 790 outputs := refreshed.Outputs 791 792 // If the provider specified new inputs for this resource, pick them up now. Otherwise, retain the current inputs. 793 inputs := s.old.Inputs 794 if refreshed.Inputs != nil { 795 inputs = refreshed.Inputs 796 } 797 798 if outputs != nil { 799 // There is a chance that the ID has changed. We want to allow this change to happen 800 // it will have changed already in the outputs, but we need to persist this change 801 // at a state level because the Id 802 if refreshed.ID != "" && refreshed.ID != resourceID { 803 logging.V(7).Infof("Refreshing ID; oldId=%s, newId=%s", resourceID, refreshed.ID) 804 resourceID = refreshed.ID 805 } 806 807 s.new = resource.NewState(s.old.Type, s.old.URN, s.old.Custom, s.old.Delete, resourceID, inputs, outputs, 808 s.old.Parent, s.old.Protect, s.old.External, s.old.Dependencies, initErrors, s.old.Provider, 809 s.old.PropertyDependencies, s.old.PendingReplacement, s.old.AdditionalSecretOutputs, s.old.Aliases, 810 &s.old.CustomTimeouts, s.old.ImportID, s.old.RetainOnDelete, s.old.DeletedWith) 811 } else { 812 s.new = nil 813 } 814 815 return rst, complete, err 816 } 817 818 type ImportStep struct { 819 deployment *Deployment // the current deployment. 820 reg RegisterResourceEvent // the registration intent to convey a URN back to. 821 original *resource.State // the original resource, if this is an import-replace. 822 old *resource.State // the state of the resource fetched from the provider. 823 new *resource.State // the newly computed state of the resource after importing. 824 replacing bool // true if we are replacing a Pulumi-managed resource. 825 planned bool // true if this import is from an import deployment. 826 diffs []resource.PropertyKey // any keys that differed between the user's program and the actual state. 827 detailedDiff map[string]plugin.PropertyDiff // the structured property diff. 828 ignoreChanges []string // a list of property paths to ignore when updating. 829 randomSeed []byte // the random seed to use for Check. 830 } 831 832 func NewImportStep(deployment *Deployment, reg RegisterResourceEvent, new *resource.State, 833 ignoreChanges []string, randomSeed []byte) Step { 834 835 contract.Assert(new != nil) 836 contract.Assert(new.URN != "") 837 contract.Assert(new.ID != "") 838 contract.Assert(new.Custom) 839 contract.Assert(!new.Delete) 840 contract.Assert(!new.External) 841 contract.Assert(randomSeed != nil) 842 843 return &ImportStep{ 844 deployment: deployment, 845 reg: reg, 846 new: new, 847 ignoreChanges: ignoreChanges, 848 randomSeed: randomSeed, 849 } 850 } 851 852 func NewImportReplacementStep(deployment *Deployment, reg RegisterResourceEvent, original, new *resource.State, 853 ignoreChanges []string, randomSeed []byte) Step { 854 855 contract.Assert(original != nil) 856 contract.Assert(new != nil) 857 contract.Assert(new.URN != "") 858 contract.Assert(new.ID != "") 859 contract.Assert(new.Custom) 860 contract.Assert(!new.Delete) 861 contract.Assert(!new.External) 862 contract.Assert(randomSeed != nil) 863 864 return &ImportStep{ 865 deployment: deployment, 866 reg: reg, 867 original: original, 868 new: new, 869 replacing: true, 870 ignoreChanges: ignoreChanges, 871 randomSeed: randomSeed, 872 } 873 } 874 875 func newImportDeploymentStep(deployment *Deployment, new *resource.State, randomSeed []byte) Step { 876 contract.Assert(new != nil) 877 contract.Assert(new.URN != "") 878 contract.Assert(new.ID != "") 879 contract.Assert(new.Custom) 880 contract.Assert(!new.Delete) 881 contract.Assert(!new.External) 882 contract.Assert(randomSeed != nil) 883 884 return &ImportStep{ 885 deployment: deployment, 886 reg: noopEvent(0), 887 new: new, 888 planned: true, 889 randomSeed: randomSeed, 890 } 891 } 892 893 func (s *ImportStep) Op() display.StepOp { 894 if s.replacing { 895 return OpImportReplacement 896 } 897 return OpImport 898 } 899 900 func (s *ImportStep) Deployment() *Deployment { return s.deployment } 901 func (s *ImportStep) Type() tokens.Type { return s.new.Type } 902 func (s *ImportStep) Provider() string { return s.new.Provider } 903 func (s *ImportStep) URN() resource.URN { return s.new.URN } 904 func (s *ImportStep) Old() *resource.State { return s.old } 905 func (s *ImportStep) New() *resource.State { return s.new } 906 func (s *ImportStep) Res() *resource.State { return s.new } 907 func (s *ImportStep) Logical() bool { return !s.replacing } 908 func (s *ImportStep) Diffs() []resource.PropertyKey { return s.diffs } 909 func (s *ImportStep) DetailedDiff() map[string]plugin.PropertyDiff { return s.detailedDiff } 910 911 func (s *ImportStep) Apply(preview bool) (resource.Status, StepCompleteFunc, error) { 912 complete := func() { s.reg.Done(&RegisterResult{State: s.new}) } 913 914 // If this is a planned import, ensure that the resource does not exist in the old state file. 915 if s.planned { 916 if _, ok := s.deployment.olds[s.new.URN]; ok { 917 return resource.StatusOK, nil, fmt.Errorf("resource '%v' already exists", s.new.URN) 918 } 919 if s.new.Parent.Type() != resource.RootStackType { 920 if _, ok := s.deployment.olds[s.new.Parent]; !ok { 921 return resource.StatusOK, nil, fmt.Errorf("unknown parent '%v' for resource '%v'", 922 s.new.Parent, s.new.URN) 923 } 924 } 925 } 926 927 // Read the current state of the resource to import. If the provider does not hand us back any inputs for the 928 // resource, it probably needs to be updated. If the resource does not exist at all, fail the import. 929 prov, err := getProvider(s) 930 if err != nil { 931 return resource.StatusOK, nil, err 932 } 933 read, rst, err := prov.Read(s.new.URN, s.new.ID, nil, nil) 934 if err != nil { 935 if initErr, isInitErr := err.(*plugin.InitError); isInitErr { 936 s.new.InitErrors = initErr.Reasons 937 } else { 938 return rst, nil, err 939 } 940 } 941 if read.Outputs == nil { 942 return rst, nil, fmt.Errorf("resource '%v' does not exist", s.new.ID) 943 } 944 if read.Inputs == nil { 945 return resource.StatusOK, nil, 946 fmt.Errorf("provider does not support importing resources; please try updating the '%v' plugin", 947 s.new.URN.Type().Package()) 948 } 949 if read.ID != "" { 950 s.new.ID = read.ID 951 } 952 s.new.Outputs = read.Outputs 953 954 // Magic up an old state so the frontend can display a proper diff. This state is the output of the just-executed 955 // `Read` combined with the resource identity and metadata from the desired state. This ensures that the only 956 // differences between the old and new states are between the inputs and outputs. 957 s.old = resource.NewState(s.new.Type, s.new.URN, s.new.Custom, false, s.new.ID, read.Inputs, read.Outputs, 958 s.new.Parent, s.new.Protect, false, s.new.Dependencies, s.new.InitErrors, s.new.Provider, 959 s.new.PropertyDependencies, false, nil, nil, &s.new.CustomTimeouts, s.new.ImportID, s.new.RetainOnDelete, 960 s.new.DeletedWith) 961 962 // If this step came from an import deployment, we need to fetch any required inputs from the state. 963 if s.planned { 964 contract.Assert(len(s.new.Inputs) == 0) 965 966 // Get the import object and see if it had properties set 967 var inputProperties []string 968 for _, imp := range s.deployment.imports { 969 if imp.ID == s.old.ID { 970 inputProperties = imp.Properties 971 break 972 } 973 } 974 975 if len(inputProperties) == 0 { 976 logging.V(9).Infof("Importing %v with all properties", s.URN()) 977 s.new.Inputs = s.old.Inputs.Copy() 978 } else { 979 logging.V(9).Infof("Importing %v with supplied properties: %v", s.URN(), inputProperties) 980 for _, p := range inputProperties { 981 k := resource.PropertyKey(p) 982 if value, has := s.old.Inputs[k]; has { 983 s.new.Inputs[k] = value 984 } 985 } 986 } 987 988 // Check the provider inputs for consistency. If the inputs fail validation, the import will still succeed, but 989 // we will display the validation failures and a message informing the user that the failures are almost 990 // definitely a provider bug. 991 _, failures, err := prov.Check(s.new.URN, s.old.Inputs, s.new.Inputs, preview, s.randomSeed) 992 if err != nil { 993 return rst, nil, err 994 } 995 996 // Print this warning before printing all the check failures to give better context. 997 if len(failures) != 0 { 998 999 // Based on if the user passed 'properties' or not we want to change the error message here. 1000 var errorMessage string 1001 if len(inputProperties) == 0 { 1002 ref, err := providers.ParseReference(s.Provider()) 1003 contract.Assert(err == nil) 1004 1005 pkgName := ref.URN().Type().Name() 1006 errorMessage = fmt.Sprintf("This is almost certainly a bug in the `%s` provider.", pkgName) 1007 } else { 1008 errorMessage = "Try specifying a different set of properties to import with in the future." 1009 } 1010 1011 s.deployment.Diag().Warningf(diag.Message(s.new.URN, 1012 "One or more imported inputs failed to validate. %s "+ 1013 "The import will still proceed, but you will need to edit the generated code after copying it into your program."), 1014 errorMessage) 1015 } 1016 1017 issueCheckFailures(s.deployment.Diag().Warningf, s.new, s.new.URN, failures) 1018 1019 s.diffs, s.detailedDiff = []resource.PropertyKey{}, map[string]plugin.PropertyDiff{} 1020 return rst, complete, err 1021 } 1022 1023 // Set inputs back to their old values (if any) for any "ignored" properties 1024 processedInputs, res := processIgnoreChanges(s.new.Inputs, s.old.Inputs, s.ignoreChanges) 1025 if res != nil { 1026 return resource.StatusOK, nil, res.Error() 1027 } 1028 s.new.Inputs = processedInputs 1029 1030 // Check the inputs using the provider inputs for defaults. 1031 inputs, failures, err := prov.Check(s.new.URN, s.old.Inputs, s.new.Inputs, preview, s.randomSeed) 1032 if err != nil { 1033 return rst, nil, err 1034 } 1035 if issueCheckErrors(s.deployment, s.new, s.new.URN, failures) { 1036 return rst, nil, errors.New("one or more inputs failed to validate") 1037 } 1038 s.new.Inputs = inputs 1039 1040 // Diff the user inputs against the provider inputs. If there are any differences, fail the import unless this step 1041 // is from an import deployment. 1042 diff, err := diffResource(s.new.URN, s.new.ID, s.old.Inputs, s.old.Outputs, s.new.Inputs, prov, preview, 1043 s.ignoreChanges) 1044 if err != nil { 1045 return rst, nil, err 1046 } 1047 1048 s.diffs, s.detailedDiff = diff.ChangedKeys, diff.DetailedDiff 1049 1050 if diff.Changes != plugin.DiffNone { 1051 const message = "inputs to import do not match the existing resource" 1052 1053 if preview { 1054 s.deployment.ctx.Diag.Warningf(diag.StreamMessage(s.new.URN, 1055 message+"; importing this resource will fail", 0)) 1056 } else { 1057 err = errors.New(message) 1058 } 1059 } 1060 1061 // If we were asked to replace an existing, non-External resource, pend the deletion here. 1062 if err == nil && s.replacing { 1063 s.original.Delete = true 1064 } 1065 1066 return rst, complete, err 1067 } 1068 1069 const ( 1070 OpSame display.StepOp = "same" // nothing to do. 1071 OpCreate display.StepOp = "create" // creating a new resource. 1072 OpUpdate display.StepOp = "update" // updating an existing resource. 1073 OpDelete display.StepOp = "delete" // deleting an existing resource. 1074 OpReplace display.StepOp = "replace" // replacing a resource with a new one. 1075 OpCreateReplacement display.StepOp = "create-replacement" // creating a new resource for a replacement. 1076 OpDeleteReplaced display.StepOp = "delete-replaced" // deleting an existing resource after replacement. 1077 OpRead display.StepOp = "read" // reading an existing resource. 1078 OpReadReplacement display.StepOp = "read-replacement" // reading an existing resource for a replacement. 1079 OpRefresh display.StepOp = "refresh" // refreshing an existing resource. 1080 OpReadDiscard display.StepOp = "discard" // removing a resource that was read. 1081 OpDiscardReplaced display.StepOp = "discard-replaced" // discarding a read resource that was replaced. 1082 OpRemovePendingReplace display.StepOp = "remove-pending-replace" // removing a pending replace resource. 1083 OpImport display.StepOp = "import" // import an existing resource. 1084 OpImportReplacement display.StepOp = "import-replacement" // replace an existing resource 1085 // with an imported resource. 1086 ) 1087 1088 // StepOps contains the full set of step operation types. 1089 var StepOps = []display.StepOp{ 1090 OpSame, 1091 OpCreate, 1092 OpUpdate, 1093 OpDelete, 1094 OpReplace, 1095 OpCreateReplacement, 1096 OpDeleteReplaced, 1097 OpRead, 1098 OpReadReplacement, 1099 OpRefresh, 1100 OpReadDiscard, 1101 OpDiscardReplaced, 1102 OpRemovePendingReplace, 1103 OpImport, 1104 OpImportReplacement, 1105 } 1106 1107 // Color returns a suggested color for lines of this op type. 1108 func Color(op display.StepOp) string { 1109 switch op { 1110 case OpSame: 1111 return colors.SpecUnimportant 1112 case OpCreate, OpImport: 1113 return colors.SpecCreate 1114 case OpDelete: 1115 return colors.SpecDelete 1116 case OpUpdate: 1117 return colors.SpecUpdate 1118 case OpReplace: 1119 return colors.SpecReplace 1120 case OpCreateReplacement: 1121 return colors.SpecCreateReplacement 1122 case OpDeleteReplaced: 1123 return colors.SpecDeleteReplaced 1124 case OpRead: 1125 return colors.SpecRead 1126 case OpReadReplacement, OpImportReplacement: 1127 return colors.SpecReplace 1128 case OpRefresh: 1129 return colors.SpecUpdate 1130 case OpReadDiscard, OpDiscardReplaced: 1131 return colors.SpecDelete 1132 default: 1133 contract.Failf("Unrecognized resource step op: '%v'", op) 1134 return "" 1135 } 1136 } 1137 1138 // ColorProgress returns a suggested coloring for lines of this of type which 1139 // are progressing. 1140 func ColorProgress(op display.StepOp) string { 1141 return colors.Bold + Color(op) 1142 } 1143 1144 // Prefix returns a suggested prefix for lines of this op type. 1145 func Prefix(op display.StepOp, done bool) string { 1146 var color string 1147 if done { 1148 color = Color(op) 1149 } else { 1150 color = ColorProgress(op) 1151 } 1152 return color + RawPrefix(op) 1153 } 1154 1155 // RawPrefix returns the uncolorized prefix text. 1156 func RawPrefix(op display.StepOp) string { 1157 switch op { 1158 case OpSame: 1159 return " " 1160 case OpCreate: 1161 return "+ " 1162 case OpDelete: 1163 return "- " 1164 case OpUpdate: 1165 return "~ " 1166 case OpReplace: 1167 return "+-" 1168 case OpCreateReplacement: 1169 return "++" 1170 case OpDeleteReplaced: 1171 return "--" 1172 case OpRead: 1173 return "> " 1174 case OpReadReplacement: 1175 return ">>" 1176 case OpRefresh: 1177 return "~ " 1178 case OpReadDiscard: 1179 return "< " 1180 case OpDiscardReplaced: 1181 return "<<" 1182 case OpImport: 1183 return "= " 1184 case OpImportReplacement: 1185 return "=>" 1186 default: 1187 contract.Failf("Unrecognized resource step op: %v", op) 1188 return "" 1189 } 1190 } 1191 1192 func PastTense(op display.StepOp) string { 1193 switch op { 1194 case OpSame, OpCreate, OpReplace, OpCreateReplacement, OpUpdate, OpReadReplacement: 1195 return string(op) + "d" 1196 case OpRefresh: 1197 return "refreshed" 1198 case OpRead: 1199 return "read" 1200 case OpReadDiscard, OpDiscardReplaced: 1201 return "discarded" 1202 case OpDelete, OpDeleteReplaced: 1203 return "deleted" 1204 case OpImport, OpImportReplacement: 1205 return "imported" 1206 default: 1207 contract.Failf("Unexpected resource step op: %v", op) 1208 return "" 1209 } 1210 } 1211 1212 // Suffix returns a suggested suffix for lines of this op type. 1213 func Suffix(op display.StepOp) string { 1214 switch op { 1215 case OpCreateReplacement, OpUpdate, OpReplace, OpReadReplacement, OpRefresh, OpImportReplacement: 1216 return colors.Reset // updates and replacements colorize individual lines; get has none 1217 } 1218 return "" 1219 } 1220 1221 // ConstrainedTo returns true if this operation is no more impactful than the constraint. 1222 func ConstrainedTo(op display.StepOp, constraint display.StepOp) bool { 1223 var allowed []display.StepOp 1224 switch constraint { 1225 case OpSame, OpDelete, OpRead, OpReadReplacement, OpRefresh, OpReadDiscard, OpDiscardReplaced, 1226 OpRemovePendingReplace, OpImport, OpImportReplacement: 1227 allowed = []display.StepOp{constraint} 1228 case OpCreate: 1229 allowed = []display.StepOp{OpSame, OpCreate} 1230 case OpUpdate: 1231 allowed = []display.StepOp{OpSame, OpUpdate} 1232 case OpReplace, OpCreateReplacement, OpDeleteReplaced: 1233 allowed = []display.StepOp{OpSame, OpUpdate, constraint} 1234 } 1235 for _, candidate := range allowed { 1236 if candidate == op { 1237 return true 1238 } 1239 } 1240 return false 1241 } 1242 1243 // getProvider fetches the provider for the given step. 1244 func getProvider(s Step) (plugin.Provider, error) { 1245 if providers.IsProviderType(s.Type()) { 1246 return s.Deployment().providers, nil 1247 } 1248 ref, err := providers.ParseReference(s.Provider()) 1249 if err != nil { 1250 return nil, fmt.Errorf("bad provider reference '%v' for resource %v: %v", s.Provider(), s.URN(), err) 1251 } 1252 if providers.IsDenyDefaultsProvider(ref) { 1253 pkg := providers.GetDeniedDefaultProviderPkg(ref) 1254 msg := diag.GetDefaultProviderDenied(s.URN()).Message 1255 return nil, fmt.Errorf(msg, pkg, s.URN()) 1256 } 1257 provider, ok := s.Deployment().GetProvider(ref) 1258 if !ok { 1259 return nil, fmt.Errorf("unknown provider '%v' for resource %v", s.Provider(), s.URN()) 1260 } 1261 return provider, nil 1262 }