pulumi/pkg/backend/display/events.go
Pat Gavlin b7404f202e
Expose update events to ExtraRuntimeValidation. (#3160)
* Add the ability to log all engine events to a file.

The path to the file can be specified using the `--event-log` flag to
the CLI. The file will be truncated if it exists. Events are written as
a list of JSON values using the schema described by `pkg/apitype`.

* Expose update engine events to ExtraRuntimeValidation.

Just what it says on the tin. Events from previews are not exposed.
2019-09-06 17:07:54 -07:00

212 lines
5.6 KiB
Go

package display
import (
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/apitype"
"github.com/pulumi/pulumi/pkg/engine"
"github.com/pulumi/pulumi/pkg/resource/plugin"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// convertEngineEvent converts a raw engine.Event into an apitype.EngineEvent used in the Pulumi
// REST API. Returns an error if the engine event is unknown or not in an expected format.
// EngineEvent.{ Sequence, Timestamp } are expected to be set by the caller.
func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
var apiEvent apitype.EngineEvent
// Error to return if the payload doesn't match expected.
eventTypePayloadMismatch := errors.Errorf("unexpected payload for event type %v", e.Type)
switch e.Type {
case engine.CancelEvent:
apiEvent.CancelEvent = &apitype.CancelEvent{}
case engine.StdoutColorEvent:
p, ok := e.Payload.(engine.StdoutEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.StdoutEvent = &apitype.StdoutEngineEvent{
Message: p.Message,
Color: string(p.Color),
}
case engine.DiagEvent:
p, ok := e.Payload.(engine.DiagEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.DiagnosticEvent = &apitype.DiagnosticEvent{
URN: string(p.URN),
Prefix: p.Prefix,
Message: p.Message,
Color: string(p.Color),
Severity: string(p.Severity),
Ephemeral: p.Ephemeral,
}
case engine.PolicyViolationEvent:
p, ok := e.Payload.(engine.PolicyViolationEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.PolicyEvent = &apitype.PolicyEvent{
ResourceURN: string(p.ResourceURN),
Message: p.Message,
Color: string(p.Color),
PolicyName: p.PolicyName,
PolicyPackName: p.PolicyPackName,
PolicyPackVersion: p.PolicyPackVersion,
EnforcementLevel: string(p.EnforcementLevel),
}
case engine.PreludeEvent:
p, ok := e.Payload.(engine.PreludeEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
// Convert the config bag.
cfg := make(map[string]string)
for k, v := range p.Config {
cfg[k] = v
}
apiEvent.PreludeEvent = &apitype.PreludeEvent{
Config: cfg,
}
case engine.SummaryEvent:
p, ok := e.Payload.(engine.SummaryEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
// Convert the resource changes.
changes := make(map[string]int)
for op, count := range p.ResourceChanges {
changes[string(op)] = count
}
apiEvent.SummaryEvent = &apitype.SummaryEvent{
MaybeCorrupt: p.MaybeCorrupt,
DurationSeconds: int(p.Duration.Seconds()),
ResourceChanges: changes,
}
case engine.ResourcePreEvent:
p, ok := e.Payload.(engine.ResourcePreEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.ResourcePreEvent = &apitype.ResourcePreEvent{
Metadata: convertStepEventMetadata(p.Metadata),
Planning: p.Planning,
}
case engine.ResourceOutputsEvent:
p, ok := e.Payload.(engine.ResourceOutputsEventPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.ResOutputsEvent = &apitype.ResOutputsEvent{
Metadata: convertStepEventMetadata(p.Metadata),
Planning: p.Planning,
}
case engine.ResourceOperationFailed:
p, ok := e.Payload.(engine.ResourceOperationFailedPayload)
if !ok {
return apiEvent, eventTypePayloadMismatch
}
apiEvent.ResOpFailedEvent = &apitype.ResOpFailedEvent{
Metadata: convertStepEventMetadata(p.Metadata),
Status: int(p.Status),
Steps: p.Steps,
}
default:
return apiEvent, errors.Errorf("unknown event type %q", e.Type)
}
return apiEvent, nil
}
func convertStepEventMetadata(md engine.StepEventMetadata) apitype.StepEventMetadata {
keys := make([]string, len(md.Keys))
for i, v := range md.Keys {
keys[i] = string(v)
}
var diffs []string
for _, v := range md.Diffs {
diffs = append(diffs, string(v))
}
var detailedDiff map[string]apitype.PropertyDiff
if md.DetailedDiff != nil {
detailedDiff = make(map[string]apitype.PropertyDiff)
for k, v := range md.DetailedDiff {
var d apitype.DiffKind
switch v.Kind {
case plugin.DiffAdd:
d = apitype.DiffAdd
case plugin.DiffAddReplace:
d = apitype.DiffAddReplace
case plugin.DiffDelete:
d = apitype.DiffDelete
case plugin.DiffDeleteReplace:
d = apitype.DiffDeleteReplace
case plugin.DiffUpdate:
d = apitype.DiffUpdate
case plugin.DiffUpdateReplace:
d = apitype.DiffUpdateReplace
default:
contract.Failf("unrecognized diff kind %v", v)
}
detailedDiff[k] = apitype.PropertyDiff{
Kind: d,
InputDiff: v.InputDiff,
}
}
}
return apitype.StepEventMetadata{
Op: string(md.Op),
URN: string(md.URN),
Type: string(md.Type),
Old: convertStepEventStateMetadata(md.Old),
New: convertStepEventStateMetadata(md.New),
Keys: keys,
Diffs: diffs,
DetailedDiff: detailedDiff,
Logical: md.Logical,
Provider: md.Provider,
}
}
func convertStepEventStateMetadata(md *engine.StepEventStateMetadata) *apitype.StepEventStateMetadata {
if md == nil {
return nil
}
inputs := make(map[string]interface{})
for k, v := range md.Inputs {
inputs[string(k)] = v
}
outputs := make(map[string]interface{})
for k, v := range md.Outputs {
outputs[string(k)] = v
}
return &apitype.StepEventStateMetadata{
Type: string(md.Type),
URN: string(md.URN),
Custom: md.Custom,
Delete: md.Delete,
ID: string(md.ID),
Parent: string(md.Parent),
Protect: md.Protect,
Inputs: inputs,
Outputs: outputs,
InitErrors: md.InitErrors,
}
}