github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/display/events.go (about) 1 package display 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "github.com/pulumi/pulumi/pkg/v3/engine" 9 "github.com/pulumi/pulumi/pkg/v3/resource/stack" 10 "github.com/pulumi/pulumi/sdk/v3/go/common/apitype" 11 "github.com/pulumi/pulumi/sdk/v3/go/common/diag" 12 "github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors" 13 "github.com/pulumi/pulumi/sdk/v3/go/common/display" 14 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 15 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/config" 16 "github.com/pulumi/pulumi/sdk/v3/go/common/resource/plugin" 17 "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" 18 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 19 ) 20 21 // ConvertEngineEvent converts a raw engine.Event into an apitype.EngineEvent used in the Pulumi 22 // REST API. Returns an error if the engine event is unknown or not in an expected format. 23 // EngineEvent.{ Sequence, Timestamp } are expected to be set by the caller. 24 // 25 // IMPORTANT: Any resource secret data stored in the engine event will be encrypted using the 26 // blinding encrypter, and unrecoverable. So this operation is inherently lossy. 27 func ConvertEngineEvent(e engine.Event, showSecrets bool) (apitype.EngineEvent, error) { 28 var apiEvent apitype.EngineEvent 29 30 // Error to return if the payload doesn't match expected. 31 eventTypePayloadMismatch := fmt.Errorf("unexpected payload for event type %v", e.Type) 32 33 switch e.Type { 34 case engine.CancelEvent: 35 apiEvent.CancelEvent = &apitype.CancelEvent{} 36 37 case engine.StdoutColorEvent: 38 p, ok := e.Payload().(engine.StdoutEventPayload) 39 if !ok { 40 return apiEvent, eventTypePayloadMismatch 41 } 42 apiEvent.StdoutEvent = &apitype.StdoutEngineEvent{ 43 Message: p.Message, 44 Color: string(p.Color), 45 } 46 47 case engine.DiagEvent: 48 p, ok := e.Payload().(engine.DiagEventPayload) 49 if !ok { 50 return apiEvent, eventTypePayloadMismatch 51 } 52 apiEvent.DiagnosticEvent = &apitype.DiagnosticEvent{ 53 URN: string(p.URN), 54 Prefix: p.Prefix, 55 Message: p.Message, 56 Color: string(p.Color), 57 Severity: string(p.Severity), 58 Ephemeral: p.Ephemeral, 59 } 60 61 case engine.PolicyViolationEvent: 62 p, ok := e.Payload().(engine.PolicyViolationEventPayload) 63 if !ok { 64 return apiEvent, eventTypePayloadMismatch 65 } 66 apiEvent.PolicyEvent = &apitype.PolicyEvent{ 67 ResourceURN: string(p.ResourceURN), 68 Message: p.Message, 69 Color: string(p.Color), 70 PolicyName: p.PolicyName, 71 PolicyPackName: p.PolicyPackName, 72 PolicyPackVersion: p.PolicyPackVersion, 73 PolicyPackVersionTag: p.PolicyPackVersion, 74 EnforcementLevel: string(p.EnforcementLevel), 75 } 76 77 case engine.PreludeEvent: 78 p, ok := e.Payload().(engine.PreludeEventPayload) 79 if !ok { 80 return apiEvent, eventTypePayloadMismatch 81 } 82 // Convert the config bag. 83 cfg := make(map[string]string) 84 for k, v := range p.Config { 85 cfg[k] = v 86 } 87 apiEvent.PreludeEvent = &apitype.PreludeEvent{ 88 Config: cfg, 89 } 90 91 case engine.SummaryEvent: 92 p, ok := e.Payload().(engine.SummaryEventPayload) 93 if !ok { 94 return apiEvent, eventTypePayloadMismatch 95 } 96 // Convert the resource changes. 97 changes := make(map[apitype.OpType]int) 98 for op, count := range p.ResourceChanges { 99 changes[apitype.OpType(op)] = count 100 } 101 apiEvent.SummaryEvent = &apitype.SummaryEvent{ 102 MaybeCorrupt: p.MaybeCorrupt, 103 DurationSeconds: int(p.Duration.Seconds()), 104 ResourceChanges: changes, 105 PolicyPacks: p.PolicyPacks, 106 } 107 108 case engine.ResourcePreEvent: 109 p, ok := e.Payload().(engine.ResourcePreEventPayload) 110 if !ok { 111 return apiEvent, eventTypePayloadMismatch 112 } 113 apiEvent.ResourcePreEvent = &apitype.ResourcePreEvent{ 114 Metadata: convertStepEventMetadata(p.Metadata, showSecrets), 115 Planning: p.Planning, 116 } 117 118 case engine.ResourceOutputsEvent: 119 p, ok := e.Payload().(engine.ResourceOutputsEventPayload) 120 if !ok { 121 return apiEvent, eventTypePayloadMismatch 122 } 123 apiEvent.ResOutputsEvent = &apitype.ResOutputsEvent{ 124 Metadata: convertStepEventMetadata(p.Metadata, showSecrets), 125 Planning: p.Planning, 126 } 127 128 case engine.ResourceOperationFailed: 129 p, ok := e.Payload().(engine.ResourceOperationFailedPayload) 130 if !ok { 131 return apiEvent, eventTypePayloadMismatch 132 } 133 apiEvent.ResOpFailedEvent = &apitype.ResOpFailedEvent{ 134 Metadata: convertStepEventMetadata(p.Metadata, showSecrets), 135 Status: int(p.Status), 136 Steps: p.Steps, 137 } 138 139 default: 140 return apiEvent, fmt.Errorf("unknown event type %q", e.Type) 141 } 142 143 return apiEvent, nil 144 } 145 146 func convertStepEventMetadata(md engine.StepEventMetadata, showSecrets bool) apitype.StepEventMetadata { 147 keys := make([]string, len(md.Keys)) 148 for i, v := range md.Keys { 149 keys[i] = string(v) 150 } 151 var diffs []string 152 for _, v := range md.Diffs { 153 diffs = append(diffs, string(v)) 154 } 155 var detailedDiff map[string]apitype.PropertyDiff 156 if md.DetailedDiff != nil { 157 detailedDiff = make(map[string]apitype.PropertyDiff) 158 for k, v := range md.DetailedDiff { 159 var d apitype.DiffKind 160 switch v.Kind { 161 case plugin.DiffAdd: 162 d = apitype.DiffAdd 163 case plugin.DiffAddReplace: 164 d = apitype.DiffAddReplace 165 case plugin.DiffDelete: 166 d = apitype.DiffDelete 167 case plugin.DiffDeleteReplace: 168 d = apitype.DiffDeleteReplace 169 case plugin.DiffUpdate: 170 d = apitype.DiffUpdate 171 case plugin.DiffUpdateReplace: 172 d = apitype.DiffUpdateReplace 173 default: 174 contract.Failf("unrecognized diff kind %v", v) 175 } 176 detailedDiff[k] = apitype.PropertyDiff{ 177 Kind: d, 178 InputDiff: v.InputDiff, 179 } 180 } 181 } 182 183 return apitype.StepEventMetadata{ 184 Op: apitype.OpType(md.Op), 185 URN: string(md.URN), 186 Type: string(md.Type), 187 188 Old: convertStepEventStateMetadata(md.Old, showSecrets), 189 New: convertStepEventStateMetadata(md.New, showSecrets), 190 191 Keys: keys, 192 Diffs: diffs, 193 DetailedDiff: detailedDiff, 194 Logical: md.Logical, 195 Provider: md.Provider, 196 } 197 } 198 199 // convertStepEventStateMetadata converts the internal StepEventStateMetadata to the API type 200 // we send over the wire. 201 // 202 // IMPORTANT: Any secret values are encrypted using the blinding encrypter. So any secret data 203 // in the resource state will be lost and unrecoverable. 204 func convertStepEventStateMetadata(md *engine.StepEventStateMetadata, 205 showSecrets bool) *apitype.StepEventStateMetadata { 206 207 if md == nil { 208 return nil 209 } 210 211 encrypter := config.BlindingCrypter 212 inputs, err := stack.SerializeProperties(md.Inputs, encrypter, showSecrets) 213 contract.IgnoreError(err) 214 215 outputs, err := stack.SerializeProperties(md.Outputs, encrypter, showSecrets) 216 contract.IgnoreError(err) 217 218 return &apitype.StepEventStateMetadata{ 219 Type: string(md.Type), 220 URN: string(md.URN), 221 222 Custom: md.Custom, 223 Delete: md.Delete, 224 ID: string(md.ID), 225 Parent: string(md.Parent), 226 Protect: md.Protect, 227 Inputs: inputs, 228 Outputs: outputs, 229 InitErrors: md.InitErrors, 230 } 231 } 232 233 // ConvertJSONEvent converts an apitype.EngineEvent from the Pulumi REST API into a raw engine.Event 234 // Returns an error if the engine event is unknown or not in an expected format. 235 // 236 // IMPORTANT: Any resource secret data stored in the engine event will be encrypted using the 237 // blinding encrypter, and unrecoverable. So this operation is inherently lossy. 238 func ConvertJSONEvent(apiEvent apitype.EngineEvent) (engine.Event, error) { 239 var event engine.Event 240 241 switch { 242 case apiEvent.CancelEvent != nil: 243 event = engine.NewEvent(engine.CancelEvent, nil) 244 245 case apiEvent.StdoutEvent != nil: 246 p := apiEvent.StdoutEvent 247 event = engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{ 248 Message: p.Message, 249 Color: colors.Colorization(p.Color), 250 }) 251 252 case apiEvent.DiagnosticEvent != nil: 253 p := apiEvent.DiagnosticEvent 254 event = engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{ 255 URN: resource.URN(p.URN), 256 Prefix: p.Prefix, 257 Message: p.Message, 258 Color: colors.Colorization(p.Color), 259 Severity: diag.Severity(p.Severity), 260 Ephemeral: p.Ephemeral, 261 }) 262 apiEvent.DiagnosticEvent = &apitype.DiagnosticEvent{} 263 264 case apiEvent.PolicyEvent != nil: 265 p := apiEvent.PolicyEvent 266 event = engine.NewEvent(engine.PolicyViolationEvent, engine.PolicyViolationEventPayload{ 267 ResourceURN: resource.URN(p.ResourceURN), 268 Message: p.Message, 269 Color: colors.Colorization(p.Color), 270 PolicyName: p.PolicyName, 271 PolicyPackName: p.PolicyPackName, 272 PolicyPackVersion: p.PolicyPackVersion, 273 EnforcementLevel: apitype.EnforcementLevel(p.EnforcementLevel), 274 }) 275 276 case apiEvent.PreludeEvent != nil: 277 p := apiEvent.PreludeEvent 278 279 // Convert the config bag. 280 event = engine.NewEvent(engine.PreludeEvent, engine.PreludeEventPayload{ 281 Config: p.Config, 282 }) 283 284 case apiEvent.SummaryEvent != nil: 285 p := apiEvent.SummaryEvent 286 // Convert the resource changes. 287 changes := display.ResourceChanges{} 288 for op, count := range p.ResourceChanges { 289 changes[display.StepOp(op)] = count 290 } 291 event = engine.NewEvent(engine.SummaryEvent, engine.SummaryEventPayload{ 292 MaybeCorrupt: p.MaybeCorrupt, 293 Duration: time.Duration(p.DurationSeconds) * time.Second, 294 ResourceChanges: changes, 295 PolicyPacks: p.PolicyPacks, 296 }) 297 298 case apiEvent.ResourcePreEvent != nil: 299 p := apiEvent.ResourcePreEvent 300 event = engine.NewEvent(engine.ResourcePreEvent, engine.ResourcePreEventPayload{ 301 Metadata: convertJSONStepEventMetadata(p.Metadata), 302 Planning: p.Planning, 303 }) 304 305 case apiEvent.ResOutputsEvent != nil: 306 p := apiEvent.ResOutputsEvent 307 event = engine.NewEvent(engine.ResourceOutputsEvent, engine.ResourceOutputsEventPayload{ 308 Metadata: convertJSONStepEventMetadata(p.Metadata), 309 Planning: p.Planning, 310 }) 311 312 case apiEvent.ResOpFailedEvent != nil: 313 p := apiEvent.ResOpFailedEvent 314 event = engine.NewEvent(engine.ResourceOperationFailed, engine.ResourceOperationFailedPayload{ 315 Metadata: convertJSONStepEventMetadata(p.Metadata), 316 Status: resource.Status(p.Status), 317 Steps: p.Steps, 318 }) 319 320 default: 321 return event, errors.New("unknown event type") 322 } 323 324 return event, nil 325 } 326 327 func convertJSONStepEventMetadata(md apitype.StepEventMetadata) engine.StepEventMetadata { 328 keys := make([]resource.PropertyKey, len(md.Keys)) 329 for i, v := range md.Keys { 330 keys[i] = resource.PropertyKey(v) 331 } 332 var diffs []resource.PropertyKey 333 for _, v := range md.Diffs { 334 diffs = append(diffs, resource.PropertyKey(v)) 335 } 336 var detailedDiff map[string]plugin.PropertyDiff 337 if md.DetailedDiff != nil { 338 detailedDiff = make(map[string]plugin.PropertyDiff) 339 for k, v := range md.DetailedDiff { 340 var d plugin.DiffKind 341 switch v.Kind { 342 case apitype.DiffAdd: 343 d = plugin.DiffAdd 344 case apitype.DiffAddReplace: 345 d = plugin.DiffAddReplace 346 case apitype.DiffDelete: 347 d = plugin.DiffDelete 348 case apitype.DiffDeleteReplace: 349 d = plugin.DiffDeleteReplace 350 case apitype.DiffUpdate: 351 d = plugin.DiffUpdate 352 case apitype.DiffUpdateReplace: 353 d = plugin.DiffUpdateReplace 354 default: 355 contract.Failf("unrecognized diff kind %v", v) 356 } 357 detailedDiff[k] = plugin.PropertyDiff{ 358 Kind: d, 359 InputDiff: v.InputDiff, 360 } 361 } 362 } 363 364 old, new := convertJSONStepEventStateMetadata(md.Old), convertJSONStepEventStateMetadata(md.New) 365 366 res := old 367 if new != nil { 368 res = new 369 } 370 371 return engine.StepEventMetadata{ 372 Op: display.StepOp(md.Op), 373 URN: resource.URN(md.URN), 374 Type: tokens.Type(md.Type), 375 376 Old: old, 377 New: new, 378 Res: res, 379 380 Keys: keys, 381 Diffs: diffs, 382 DetailedDiff: detailedDiff, 383 Logical: md.Logical, 384 Provider: md.Provider, 385 } 386 } 387 388 // convertJSONStepEventStateMetadata converts the internal StepEventStateMetadata to the API type 389 // we send over the wire. 390 // 391 // IMPORTANT: Any secret values are encrypted using the blinding encrypter. So any secret data 392 // in the resource state will be lost and unrecoverable. 393 func convertJSONStepEventStateMetadata(md *apitype.StepEventStateMetadata) *engine.StepEventStateMetadata { 394 if md == nil { 395 return nil 396 } 397 398 crypter := config.BlindingCrypter 399 inputs, err := stack.DeserializeProperties(md.Inputs, crypter, crypter) 400 contract.IgnoreError(err) 401 402 outputs, err := stack.DeserializeProperties(md.Outputs, crypter, crypter) 403 contract.IgnoreError(err) 404 405 return &engine.StepEventStateMetadata{ 406 Type: tokens.Type(md.Type), 407 URN: resource.URN(md.URN), 408 409 Custom: md.Custom, 410 Delete: md.Delete, 411 ID: resource.ID(md.ID), 412 Parent: resource.URN(md.Parent), 413 Protect: md.Protect, 414 Inputs: inputs, 415 Outputs: outputs, 416 InitErrors: md.InitErrors, 417 } 418 }