github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/engine/events.go (about) 1 // Copyright 2016-2022, 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 engine 16 17 import ( 18 "bytes" 19 "time" 20 21 "github.com/pulumi/pulumi/pkg/v3/resource/deploy" 22 "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" 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/config" 28 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 29 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 30 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 31 "github.com/pulumi/pulumi/sdk/v3/go/common/util/deepcopy" 32 "github.com/pulumi/pulumi/sdk/v3/go/common/util/logging" 33 ) 34 35 // Event represents an event generated by the engine during an operation. The underlying 36 // type for the `Payload` field will differ depending on the value of the `Type` field 37 type Event struct { 38 Type EventType 39 payload interface{} 40 } 41 42 func NewEvent(typ EventType, payload interface{}) Event { 43 ok := false 44 switch typ { 45 case CancelEvent: 46 ok = payload == nil 47 case StdoutColorEvent: 48 _, ok = payload.(StdoutEventPayload) 49 case DiagEvent: 50 _, ok = payload.(DiagEventPayload) 51 case PreludeEvent: 52 _, ok = payload.(PreludeEventPayload) 53 case SummaryEvent: 54 _, ok = payload.(SummaryEventPayload) 55 case ResourcePreEvent: 56 _, ok = payload.(ResourcePreEventPayload) 57 case ResourceOutputsEvent: 58 _, ok = payload.(ResourceOutputsEventPayload) 59 case ResourceOperationFailed: 60 _, ok = payload.(ResourceOperationFailedPayload) 61 case PolicyViolationEvent: 62 _, ok = payload.(PolicyViolationEventPayload) 63 default: 64 contract.Failf("unknown event type %v", typ) 65 } 66 contract.Assertf(ok, "invalid payload of type %T for event type %v", payload, typ) 67 return Event{ 68 Type: typ, 69 payload: payload, 70 } 71 } 72 73 // EventType is the kind of event being emitted. 74 type EventType string 75 76 const ( 77 CancelEvent EventType = "cancel" 78 StdoutColorEvent EventType = "stdoutcolor" 79 DiagEvent EventType = "diag" 80 PreludeEvent EventType = "prelude" 81 SummaryEvent EventType = "summary" 82 ResourcePreEvent EventType = "resource-pre" 83 ResourceOutputsEvent EventType = "resource-outputs" 84 ResourceOperationFailed EventType = "resource-operationfailed" 85 PolicyViolationEvent EventType = "policy-violation" 86 ) 87 88 func (e Event) Payload() interface{} { 89 return deepcopy.Copy(e.payload) 90 } 91 92 func cancelEvent() Event { 93 return Event{Type: CancelEvent} 94 } 95 96 // DiagEventPayload is the payload for an event with type `diag` 97 type DiagEventPayload struct { 98 URN resource.URN 99 Prefix string 100 Message string 101 Color colors.Colorization 102 Severity diag.Severity 103 StreamID int32 104 Ephemeral bool 105 } 106 107 // PolicyViolationEventPayload is the payload for an event with type `policy-violation`. 108 type PolicyViolationEventPayload struct { 109 ResourceURN resource.URN 110 Message string 111 Color colors.Colorization 112 PolicyName string 113 PolicyPackName string 114 PolicyPackVersion string 115 EnforcementLevel apitype.EnforcementLevel 116 Prefix string 117 } 118 119 type StdoutEventPayload struct { 120 Message string 121 Color colors.Colorization 122 } 123 124 type PreludeEventPayload struct { 125 IsPreview bool // true if this prelude is for a plan operation 126 Config map[string]string // the keys and values for config. For encrypted config, the values may be blinded 127 } 128 129 type SummaryEventPayload struct { 130 IsPreview bool // true if this summary is for a plan operation 131 MaybeCorrupt bool // true if one or more resources may be corrupt 132 Duration time.Duration // the duration of the entire update operation (zero values for previews) 133 ResourceChanges display.ResourceChanges // count of changed resources, useful for reporting 134 PolicyPacks map[string]string // {policy-pack: version} for each policy pack applied 135 } 136 137 type ResourceOperationFailedPayload struct { 138 Metadata StepEventMetadata 139 Status resource.Status 140 Steps int 141 } 142 143 type ResourceOutputsEventPayload struct { 144 Metadata StepEventMetadata 145 Planning bool 146 Debug bool 147 } 148 149 type ResourcePreEventPayload struct { 150 Metadata StepEventMetadata 151 Planning bool 152 Debug bool 153 } 154 155 // StepEventMetadata contains the metadata associated with a step the engine is performing. 156 type StepEventMetadata struct { 157 Op display.StepOp // the operation performed by this step. 158 URN resource.URN // the resource URN (for before and after). 159 Type tokens.Type // the type affected by this step. 160 Old *StepEventStateMetadata // the state of the resource before performing this step. 161 New *StepEventStateMetadata // the state of the resource after performing this step. 162 Res *StepEventStateMetadata // the latest state for the resource that is known (worst case, old). 163 Keys []resource.PropertyKey // the keys causing replacement (only for CreateStep and ReplaceStep). 164 Diffs []resource.PropertyKey // the keys causing diffs 165 DetailedDiff map[string]plugin.PropertyDiff // the rich, structured diff 166 Logical bool // true if this step represents a logical operation in the program. 167 Provider string // the provider that performed this step. 168 } 169 170 // StepEventStateMetadata contains detailed metadata about a resource's state pertaining to a given step. 171 type StepEventStateMetadata struct { 172 // State contains the raw, complete state, for this resource. 173 State *resource.State 174 // the resource's type. 175 Type tokens.Type 176 // the resource's object urn, a human-friendly, unique name for the resource. 177 URN resource.URN 178 // true if the resource is custom, managed by a plugin. 179 Custom bool 180 // true if this resource is pending deletion due to a replacement. 181 Delete bool 182 // the resource's unique ID, assigned by the resource provider (or blank if none/uncreated). 183 ID resource.ID 184 // an optional parent URN that this resource belongs to. 185 Parent resource.URN 186 // true to "protect" this resource (protected resources cannot be deleted). 187 Protect bool 188 // the resource's input properties (as specified by the program). Note: because this will cross 189 // over rpc boundaries it will be slightly different than the Inputs found in resource_state. 190 // Specifically, secrets will have been filtered out, and large values (like assets) will be 191 // have a simple hash-based representation. This allows clients to display this information 192 // properly, without worrying about leaking sensitive data, and without having to transmit huge 193 // amounts of data. 194 Inputs resource.PropertyMap 195 // the resource's complete output state (as returned by the resource provider). See "Inputs" 196 // for additional details about how data will be transformed before going into this map. 197 Outputs resource.PropertyMap 198 // the resource's provider reference 199 Provider string 200 // InitErrors is the set of errors encountered in the process of initializing resource (i.e., 201 // during create or update). 202 InitErrors []string 203 } 204 205 func makeEventEmitter(events chan<- Event, update UpdateInfo) (eventEmitter, error) { 206 target := update.GetTarget() 207 var secrets []string 208 if target != nil && target.Config.HasSecureValue() { 209 for k, v := range target.Config { 210 if !v.Secure() { 211 continue 212 } 213 214 secureValues, err := v.SecureValues(target.Decrypter) 215 if err != nil { 216 return eventEmitter{}, DecryptError{ 217 Key: k, 218 Err: err, 219 } 220 } 221 secrets = append(secrets, secureValues...) 222 } 223 } 224 225 logging.AddGlobalFilter(logging.CreateFilter(secrets, "[secret]")) 226 227 buffer, done := make(chan Event), make(chan bool) 228 go queueEvents(events, buffer, done) 229 230 return eventEmitter{ 231 done: done, 232 ch: buffer, 233 }, nil 234 } 235 236 func makeQueryEventEmitter(events chan<- Event) (eventEmitter, error) { 237 buffer, done := make(chan Event), make(chan bool) 238 239 go queueEvents(events, buffer, done) 240 241 return eventEmitter{ 242 done: done, 243 ch: buffer, 244 }, nil 245 } 246 247 type eventEmitter struct { 248 done <-chan bool 249 ch chan<- Event 250 } 251 252 func queueEvents(events chan<- Event, buffer chan Event, done chan bool) { 253 // Instead of sending to the source channel directly, buffer events to account for slow receivers. 254 // 255 // Buffering is done by a goroutine that concurrently receives from the senders and attempts to send events to the 256 // receiver. Events that are received while waiting for the receiver to catch up are buffered in a slice. 257 // 258 // We do not use a buffered channel because it is empirically less likely that the goroutine reading from a 259 // buffered channel will be scheduled when new data is placed in the channel. 260 261 defer close(done) 262 263 var queue []Event 264 for { 265 contract.Assert(buffer != nil) 266 267 e, ok := <-buffer 268 if !ok { 269 return 270 } 271 queue = append(queue, e) 272 273 // While there are events in the queue, attempt to send them to the waiting receiver. If the receiver is 274 // blocked and an event is received from the event senders, stick that event in the queue. 275 for len(queue) > 0 { 276 select { 277 case e, ok := <-buffer: 278 if !ok { 279 // If the event source has been closed, flush the queue. 280 for _, e := range queue { 281 trySendEvent(events, e) 282 } 283 return 284 } 285 queue = append(queue, e) 286 case events <- queue[0]: 287 queue = queue[1:] 288 } 289 } 290 } 291 } 292 293 func makeStepEventMetadata(op display.StepOp, step deploy.Step, debug bool) StepEventMetadata { 294 contract.Assert(op == step.Op() || step.Op() == deploy.OpRefresh) 295 296 var keys, diffs []resource.PropertyKey 297 if keyer, hasKeys := step.(interface{ Keys() []resource.PropertyKey }); hasKeys { 298 keys = keyer.Keys() 299 } 300 if differ, hasDiffs := step.(interface{ Diffs() []resource.PropertyKey }); hasDiffs { 301 diffs = differ.Diffs() 302 } 303 304 var detailedDiff map[string]plugin.PropertyDiff 305 if detailedDiffer, hasDetailedDiff := step.(interface { 306 DetailedDiff() map[string]plugin.PropertyDiff 307 }); hasDetailedDiff { 308 detailedDiff = detailedDiffer.DetailedDiff() 309 } 310 311 return StepEventMetadata{ 312 Op: op, 313 URN: step.URN(), 314 Type: step.Type(), 315 Keys: keys, 316 Diffs: diffs, 317 DetailedDiff: detailedDiff, 318 Old: makeStepEventStateMetadata(step.Old(), debug), 319 New: makeStepEventStateMetadata(step.New(), debug), 320 Res: makeStepEventStateMetadata(step.Res(), debug), 321 Logical: step.Logical(), 322 Provider: step.Provider(), 323 } 324 } 325 326 func makeStepEventStateMetadata(state *resource.State, debug bool) *StepEventStateMetadata { 327 if state == nil { 328 return nil 329 } 330 331 return &StepEventStateMetadata{ 332 State: state, 333 Type: state.Type, 334 URN: state.URN, 335 Custom: state.Custom, 336 Delete: state.Delete, 337 ID: state.ID, 338 Parent: state.Parent, 339 Protect: state.Protect, 340 Inputs: filterResourceProperties(state.Inputs, debug), 341 Outputs: filterResourceProperties(state.Outputs, debug), 342 Provider: state.Provider, 343 InitErrors: state.InitErrors, 344 } 345 } 346 347 func (e *eventEmitter) Close() { 348 tryCloseEventChan(e.ch) 349 <-e.done 350 } 351 352 func (e *eventEmitter) sendEvent(event Event) { 353 trySendEvent(e.ch, event) 354 } 355 356 func (e *eventEmitter) resourceOperationFailedEvent( 357 step deploy.Step, status resource.Status, steps int, debug bool) { 358 359 contract.Requiref(e != nil, "e", "!= nil") 360 361 e.sendEvent(NewEvent(ResourceOperationFailed, ResourceOperationFailedPayload{ 362 Metadata: makeStepEventMetadata(step.Op(), step, debug), 363 Status: status, 364 Steps: steps, 365 })) 366 } 367 368 func (e *eventEmitter) resourceOutputsEvent(op display.StepOp, step deploy.Step, planning bool, debug bool) { 369 contract.Requiref(e != nil, "e", "!= nil") 370 371 e.sendEvent(NewEvent(ResourceOutputsEvent, ResourceOutputsEventPayload{ 372 Metadata: makeStepEventMetadata(op, step, debug), 373 Planning: planning, 374 Debug: debug, 375 })) 376 } 377 378 func (e *eventEmitter) resourcePreEvent( 379 step deploy.Step, planning bool, debug bool) { 380 381 contract.Requiref(e != nil, "e", "!= nil") 382 383 e.sendEvent(NewEvent(ResourcePreEvent, ResourcePreEventPayload{ 384 Metadata: makeStepEventMetadata(step.Op(), step, debug), 385 Planning: planning, 386 Debug: debug, 387 })) 388 } 389 390 func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) { 391 contract.Requiref(e != nil, "e", "!= nil") 392 393 configStringMap := make(map[string]string, len(cfg)) 394 for k, v := range cfg { 395 keyString := k.String() 396 valueString, err := v.Value(config.NewBlindingDecrypter()) 397 contract.AssertNoError(err) 398 configStringMap[keyString] = valueString 399 } 400 401 e.sendEvent(NewEvent(PreludeEvent, PreludeEventPayload{ 402 IsPreview: isPreview, 403 Config: configStringMap, 404 })) 405 } 406 407 func (e *eventEmitter) summaryEvent(preview, maybeCorrupt bool, duration time.Duration, 408 resourceChanges display.ResourceChanges, policyPacks map[string]string) { 409 410 contract.Requiref(e != nil, "e", "!= nil") 411 412 e.sendEvent(NewEvent(SummaryEvent, SummaryEventPayload{ 413 IsPreview: preview, 414 MaybeCorrupt: maybeCorrupt, 415 Duration: duration, 416 ResourceChanges: resourceChanges, 417 PolicyPacks: policyPacks, 418 })) 419 } 420 421 func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDiagnostic) { 422 423 contract.Requiref(e != nil, "e", "!= nil") 424 425 // Write prefix. 426 var prefix bytes.Buffer 427 switch d.EnforcementLevel { 428 case apitype.Mandatory: 429 prefix.WriteString(colors.SpecError) 430 case apitype.Advisory: 431 prefix.WriteString(colors.SpecWarning) 432 default: 433 contract.Failf("Unrecognized diagnostic severity: %v", d) 434 } 435 436 prefix.WriteString(string(d.EnforcementLevel)) 437 prefix.WriteString(": ") 438 prefix.WriteString(colors.Reset) 439 440 // Write the message itself. 441 var buffer bytes.Buffer 442 buffer.WriteString(colors.SpecNote) 443 444 buffer.WriteString(d.Message) 445 446 buffer.WriteString(colors.Reset) 447 buffer.WriteRune('\n') 448 449 e.sendEvent(NewEvent(PolicyViolationEvent, PolicyViolationEventPayload{ 450 ResourceURN: urn, 451 Message: logging.FilterString(buffer.String()), 452 Color: colors.Raw, 453 PolicyName: d.PolicyName, 454 PolicyPackName: d.PolicyPackName, 455 PolicyPackVersion: d.PolicyPackVersion, 456 EnforcementLevel: d.EnforcementLevel, 457 Prefix: logging.FilterString(prefix.String()), 458 })) 459 } 460 461 func diagEvent(e *eventEmitter, d *diag.Diag, prefix, msg string, sev diag.Severity, 462 ephemeral bool) { 463 contract.Requiref(e != nil, "e", "!= nil") 464 465 e.sendEvent(NewEvent(DiagEvent, DiagEventPayload{ 466 URN: d.URN, 467 Prefix: logging.FilterString(prefix), 468 Message: logging.FilterString(msg), 469 Color: colors.Raw, 470 Severity: sev, 471 StreamID: d.StreamID, 472 Ephemeral: ephemeral, 473 })) 474 } 475 476 func (e *eventEmitter) diagDebugEvent(d *diag.Diag, prefix, msg string, ephemeral bool) { 477 diagEvent(e, d, prefix, msg, diag.Debug, ephemeral) 478 } 479 480 func (e *eventEmitter) diagInfoEvent(d *diag.Diag, prefix, msg string, ephemeral bool) { 481 diagEvent(e, d, prefix, msg, diag.Info, ephemeral) 482 } 483 484 func (e *eventEmitter) diagInfoerrEvent(d *diag.Diag, prefix, msg string, ephemeral bool) { 485 diagEvent(e, d, prefix, msg, diag.Infoerr, ephemeral) 486 } 487 488 func (e *eventEmitter) diagErrorEvent(d *diag.Diag, prefix, msg string, ephemeral bool) { 489 diagEvent(e, d, prefix, msg, diag.Error, ephemeral) 490 } 491 492 func (e *eventEmitter) diagWarningEvent(d *diag.Diag, prefix, msg string, ephemeral bool) { 493 diagEvent(e, d, prefix, msg, diag.Warning, ephemeral) 494 } 495 496 func filterResourceProperties(m resource.PropertyMap, debug bool) resource.PropertyMap { 497 return filterPropertyValue(resource.NewObjectProperty(m), debug).ObjectValue() 498 } 499 500 func filterPropertyValue(v resource.PropertyValue, debug bool) resource.PropertyValue { 501 switch { 502 case v.IsNull(), v.IsBool(), v.IsNumber(): 503 return v 504 case v.IsString(): 505 // have to ensure we filter out secrets. 506 return resource.NewStringProperty(logging.FilterString(v.StringValue())) 507 case v.IsAsset(): 508 return resource.NewAssetProperty(filterAsset(v.AssetValue(), debug)) 509 case v.IsArchive(): 510 return resource.NewArchiveProperty(filterArchive(v.ArchiveValue(), debug)) 511 case v.IsArray(): 512 arr := make([]resource.PropertyValue, len(v.ArrayValue())) 513 for i, v := range v.ArrayValue() { 514 arr[i] = filterPropertyValue(v, debug) 515 } 516 return resource.NewArrayProperty(arr) 517 case v.IsObject(): 518 obj := make(resource.PropertyMap, len(v.ObjectValue())) 519 for k, v := range v.ObjectValue() { 520 obj[k] = filterPropertyValue(v, debug) 521 } 522 return resource.NewObjectProperty(obj) 523 case v.IsComputed(): 524 return resource.MakeComputed(filterPropertyValue(v.Input().Element, debug)) 525 case v.IsOutput(): 526 return resource.MakeComputed(filterPropertyValue(v.OutputValue().Element, debug)) 527 case v.IsSecret(): 528 return resource.MakeSecret(resource.NewStringProperty("[secret]")) 529 case v.IsResourceReference(): 530 ref := v.ResourceReferenceValue() 531 return resource.NewResourceReferenceProperty(resource.ResourceReference{ 532 URN: resource.URN(logging.FilterString(string(ref.URN))), 533 ID: filterPropertyValue(ref.ID, debug), 534 PackageVersion: logging.FilterString(ref.PackageVersion), 535 }) 536 default: 537 contract.Failf("unexpected property value type %T", v.V) 538 return resource.PropertyValue{} 539 } 540 } 541 542 func filterAsset(v *resource.Asset, debug bool) *resource.Asset { 543 if !v.IsText() { 544 return v 545 } 546 547 // we don't want to include the full text of an asset as we serialize it over as 548 // events. They represent user files and are thus are unbounded in size. Instead, 549 // we only include the text if it represents a user's serialized program code, as 550 // that is something we want the receiver to see to display as part of 551 // progress/diffs/etc. 552 var text string 553 if v.IsUserProgramCode() { 554 // also make sure we filter this in case there are any secrets in the code. 555 text = logging.FilterString(resource.MassageIfUserProgramCodeAsset(v, debug).Text) 556 } else { 557 // We need to have some string here so that we preserve that this is a 558 // text-asset 559 text = "<contents elided>" 560 } 561 562 return &resource.Asset{ 563 Sig: v.Sig, 564 Hash: v.Hash, 565 Text: text, 566 } 567 } 568 569 func filterArchive(v *resource.Archive, debug bool) *resource.Archive { 570 if !v.IsAssets() { 571 return v 572 } 573 574 assets := make(map[string]interface{}) 575 for k, v := range v.Assets { 576 switch v := v.(type) { 577 case *resource.Asset: 578 assets[k] = filterAsset(v, debug) 579 case *resource.Archive: 580 assets[k] = filterArchive(v, debug) 581 default: 582 contract.Failf("Unrecognized asset map type %T", v) 583 } 584 } 585 return &resource.Archive{ 586 Sig: v.Sig, 587 Hash: v.Hash, 588 Assets: assets, 589 } 590 } 591 592 // Sends an event like a normal send but recovers from a panic on a 593 // closed channel. This is generally a design smell and should be used 594 // very sparingly and every use of this function needs to document the 595 // need. 596 // 597 // eventEmitter uses tryEventSend to recover in the scenario of 598 // cancelSource.Terminate being called (such as user pressing Ctrl+C 599 // twice), when straggler stepExecutor workers are sending diag events 600 // but the engine is shutting down. 601 // 602 // See https://github.com/pulumi/pulumi/issues/10431 for the details. 603 func trySendEvent(ch chan<- Event, ev Event) (sent bool) { 604 sent = true 605 defer func() { 606 if recover() != nil { 607 sent = false 608 if logging.V(9) { 609 logging.V(9).Infof( 610 "Ignoring %v send on a closed channel %p", 611 ev.Type, ch) 612 } 613 } 614 }() 615 ch <- ev 616 return sent 617 } 618 619 // Tries to close a channel but recovers from a panic of closing a 620 // closed channel. Restrictions on use are similarly to those of 621 // trySendEvent. 622 func tryCloseEventChan(ch chan<- Event) (closed bool) { 623 closed = true 624 defer func() { 625 if recover() != nil { 626 closed = false 627 if logging.V(9) { 628 logging.V(9).Infof( 629 "Ignoring close of a closed event channel %p", 630 ch) 631 } 632 } 633 }() 634 close(ch) 635 return closed 636 }