Protect against engine event mutation. (#5003)
Certain operations in `engine/diff` mutate engine events during display. This mutation can occur concurrently with the serialization of the event for persistence, which causes a panic in the CLI. These changes fix the offending code and add code that copies each engine event before persisteing it in order to guard against future issues.
This commit is contained in:
parent
67525ee4b1
commit
418e2291a2
|
@ -68,7 +68,7 @@ func ShowDiffEvents(op string, action apitype.UpdateKind,
|
|||
|
||||
out := os.Stdout
|
||||
if event.Type == engine.DiagEvent {
|
||||
payload := event.Payload.(engine.DiagEventPayload)
|
||||
payload := event.Payload().(engine.DiagEventPayload)
|
||||
if payload.Severity == diag.Error || payload.Severity == diag.Warning {
|
||||
out = os.Stderr
|
||||
}
|
||||
|
@ -96,25 +96,26 @@ func RenderDiffEvent(action apitype.UpdateKind, event engine.Event,
|
|||
// Currently, prelude, summary, and stdout events are printed the same for both the diff and
|
||||
// progress displays.
|
||||
case engine.PreludeEvent:
|
||||
return renderPreludeEvent(event.Payload.(engine.PreludeEventPayload), opts)
|
||||
return renderPreludeEvent(event.Payload().(engine.PreludeEventPayload), opts)
|
||||
case engine.SummaryEvent:
|
||||
return renderSummaryEvent(action, event.Payload.(engine.SummaryEventPayload), false /* wroteDiagnosticHeader */, opts)
|
||||
const wroteDiagnosticHeader = false
|
||||
return renderSummaryEvent(action, event.Payload().(engine.SummaryEventPayload), wroteDiagnosticHeader, opts)
|
||||
case engine.StdoutColorEvent:
|
||||
return renderStdoutColorEvent(event.Payload.(engine.StdoutEventPayload), opts)
|
||||
return renderStdoutColorEvent(event.Payload().(engine.StdoutEventPayload), opts)
|
||||
|
||||
// Resource operations have very specific displays for either diff or progress displays.
|
||||
// These functions should not be directly used by the progress display without validating
|
||||
// that the display is appropriate for both.
|
||||
case engine.ResourceOperationFailed:
|
||||
return renderDiffResourceOperationFailedEvent(event.Payload.(engine.ResourceOperationFailedPayload), opts)
|
||||
return renderDiffResourceOperationFailedEvent(event.Payload().(engine.ResourceOperationFailedPayload), opts)
|
||||
case engine.ResourceOutputsEvent:
|
||||
return renderDiffResourceOutputsEvent(event.Payload.(engine.ResourceOutputsEventPayload), seen, opts)
|
||||
return renderDiffResourceOutputsEvent(event.Payload().(engine.ResourceOutputsEventPayload), seen, opts)
|
||||
case engine.ResourcePreEvent:
|
||||
return renderDiffResourcePreEvent(event.Payload.(engine.ResourcePreEventPayload), seen, opts)
|
||||
return renderDiffResourcePreEvent(event.Payload().(engine.ResourcePreEventPayload), seen, opts)
|
||||
case engine.DiagEvent:
|
||||
return renderDiffDiagEvent(event.Payload.(engine.DiagEventPayload), opts)
|
||||
return renderDiffDiagEvent(event.Payload().(engine.DiagEventPayload), opts)
|
||||
case engine.PolicyViolationEvent:
|
||||
return renderDiffPolicyViolationEvent(event.Payload.(engine.PolicyViolationEventPayload), opts)
|
||||
return renderDiffPolicyViolationEvent(event.Payload().(engine.PolicyViolationEventPayload), opts)
|
||||
|
||||
default:
|
||||
contract.Failf("unknown event type '%s'", event.Type)
|
||||
|
|
|
@ -28,7 +28,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
apiEvent.CancelEvent = &apitype.CancelEvent{}
|
||||
|
||||
case engine.StdoutColorEvent:
|
||||
p, ok := e.Payload.(engine.StdoutEventPayload)
|
||||
p, ok := e.Payload().(engine.StdoutEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.DiagEvent:
|
||||
p, ok := e.Payload.(engine.DiagEventPayload)
|
||||
p, ok := e.Payload().(engine.DiagEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.PolicyViolationEvent:
|
||||
p, ok := e.Payload.(engine.PolicyViolationEventPayload)
|
||||
p, ok := e.Payload().(engine.PolicyViolationEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -68,7 +68,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.PreludeEvent:
|
||||
p, ok := e.Payload.(engine.PreludeEventPayload)
|
||||
p, ok := e.Payload().(engine.PreludeEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.SummaryEvent:
|
||||
p, ok := e.Payload.(engine.SummaryEventPayload)
|
||||
p, ok := e.Payload().(engine.SummaryEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.ResourcePreEvent:
|
||||
p, ok := e.Payload.(engine.ResourcePreEventPayload)
|
||||
p, ok := e.Payload().(engine.ResourcePreEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.ResourceOutputsEvent:
|
||||
p, ok := e.Payload.(engine.ResourceOutputsEventPayload)
|
||||
p, ok := e.Payload().(engine.ResourceOutputsEventPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ func ConvertEngineEvent(e engine.Event) (apitype.EngineEvent, error) {
|
|||
}
|
||||
|
||||
case engine.ResourceOperationFailed:
|
||||
p, ok := e.Payload.(engine.ResourceOperationFailedPayload)
|
||||
p, ok := e.Payload().(engine.ResourceOperationFailedPayload)
|
||||
if !ok {
|
||||
return apiEvent, eventTypePayloadMismatch
|
||||
}
|
||||
|
|
|
@ -110,12 +110,12 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
|
|||
// Events ocurring early:
|
||||
case engine.PreludeEvent:
|
||||
// Capture the config map from the prelude. Note that all secrets will remain blinded for safety.
|
||||
digest.Config = e.Payload.(engine.PreludeEventPayload).Config
|
||||
digest.Config = e.Payload().(engine.PreludeEventPayload).Config
|
||||
|
||||
// Events throughout the execution:
|
||||
case engine.DiagEvent:
|
||||
// Skip any ephemeral or debug messages, and elide all colorization.
|
||||
p := e.Payload.(engine.DiagEventPayload)
|
||||
p := e.Payload().(engine.DiagEventPayload)
|
||||
if !p.Ephemeral && p.Severity != diag.Debug {
|
||||
digest.Diagnostics = append(digest.Diagnostics, previewDiagnostic{
|
||||
URN: p.URN,
|
||||
|
@ -125,7 +125,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
|
|||
}
|
||||
case engine.StdoutColorEvent:
|
||||
// Append stdout events as informational messages, and elide all colorization.
|
||||
p := e.Payload.(engine.StdoutEventPayload)
|
||||
p := e.Payload().(engine.StdoutEventPayload)
|
||||
digest.Diagnostics = append(digest.Diagnostics, previewDiagnostic{
|
||||
Message: colors.Never.Colorize(p.Message),
|
||||
Severity: diag.Info,
|
||||
|
@ -133,7 +133,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
|
|||
case engine.ResourcePreEvent:
|
||||
// Create the detailed metadata for this step and the initial state of its resource. Later,
|
||||
// if new outputs arrive, we'll search for and swap in those new values.
|
||||
if m := e.Payload.(engine.ResourcePreEventPayload).Metadata; shouldShow(m, opts) || isRootStack(m) {
|
||||
if m := e.Payload().(engine.ResourcePreEventPayload).Metadata; shouldShow(m, opts) || isRootStack(m) {
|
||||
var detailedDiff map[string]propertyDiff
|
||||
if m.DetailedDiff != nil {
|
||||
detailedDiff = make(map[string]propertyDiff)
|
||||
|
@ -183,7 +183,7 @@ func ShowJSONEvents(op string, action apitype.UpdateKind, events <-chan engine.E
|
|||
// Events ocurring late:
|
||||
case engine.SummaryEvent:
|
||||
// At the end of the preview, a summary event indicates the final conclusions.
|
||||
p := e.Payload.(engine.SummaryEventPayload)
|
||||
p := e.Payload().(engine.SummaryEventPayload)
|
||||
digest.Duration = p.Duration
|
||||
digest.ChangeSummary = p.ResourceChanges
|
||||
digest.MaybeCorrupt = p.MaybeCorrupt
|
||||
|
|
|
@ -183,22 +183,23 @@ func simplifyTypeName(typ tokens.Type) string {
|
|||
// event that has a URN. If this is also a 'step' event, then this will return the step metadata as
|
||||
// well.
|
||||
func getEventUrnAndMetadata(event engine.Event) (resource.URN, *engine.StepEventMetadata) {
|
||||
if event.Type == engine.ResourcePreEvent {
|
||||
payload := event.Payload.(engine.ResourcePreEventPayload)
|
||||
switch event.Type {
|
||||
case engine.ResourcePreEvent:
|
||||
payload := event.Payload().(engine.ResourcePreEventPayload)
|
||||
return payload.Metadata.URN, &payload.Metadata
|
||||
} else if event.Type == engine.ResourceOutputsEvent {
|
||||
payload := event.Payload.(engine.ResourceOutputsEventPayload)
|
||||
case engine.ResourceOutputsEvent:
|
||||
payload := event.Payload().(engine.ResourceOutputsEventPayload)
|
||||
return payload.Metadata.URN, &payload.Metadata
|
||||
} else if event.Type == engine.ResourceOperationFailed {
|
||||
payload := event.Payload.(engine.ResourceOperationFailedPayload)
|
||||
case engine.ResourceOperationFailed:
|
||||
payload := event.Payload().(engine.ResourceOperationFailedPayload)
|
||||
return payload.Metadata.URN, &payload.Metadata
|
||||
} else if event.Type == engine.DiagEvent {
|
||||
return event.Payload.(engine.DiagEventPayload).URN, nil
|
||||
} else if event.Type == engine.PolicyViolationEvent {
|
||||
return event.Payload.(engine.PolicyViolationEventPayload).ResourceURN, nil
|
||||
case engine.DiagEvent:
|
||||
return event.Payload().(engine.DiagEventPayload).URN, nil
|
||||
case engine.PolicyViolationEvent:
|
||||
return event.Payload().(engine.PolicyViolationEventPayload).ResourceURN, nil
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Converts the colorization tags in a progress message and then actually writes the progress
|
||||
|
@ -962,18 +963,15 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
|||
// A prelude event can just be printed out directly to the console.
|
||||
// Note: we should probably make sure we don't get any prelude events
|
||||
// once we start hearing about actual resource events.
|
||||
payload := event.Payload.(engine.PreludeEventPayload)
|
||||
payload := event.Payload().(engine.PreludeEventPayload)
|
||||
preludeEventString := renderPreludeEvent(payload, display.opts)
|
||||
if display.isTerminal {
|
||||
display.processNormalEvent(engine.Event{
|
||||
Type: engine.DiagEvent,
|
||||
Payload: engine.DiagEventPayload{
|
||||
Ephemeral: false,
|
||||
Severity: diag.Info,
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
Message: preludeEventString,
|
||||
},
|
||||
})
|
||||
display.processNormalEvent(engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{
|
||||
Ephemeral: false,
|
||||
Severity: diag.Info,
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
Message: preludeEventString,
|
||||
}))
|
||||
} else {
|
||||
display.writeSimpleMessage(preludeEventString)
|
||||
}
|
||||
|
@ -981,16 +979,16 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
|||
case engine.SummaryEvent:
|
||||
// keep track of the summary event so that we can display it after all other
|
||||
// resource-related events we receive.
|
||||
payload := event.Payload.(engine.SummaryEventPayload)
|
||||
payload := event.Payload().(engine.SummaryEventPayload)
|
||||
display.summaryEventPayload = &payload
|
||||
return
|
||||
case engine.DiagEvent:
|
||||
msg := display.renderProgressDiagEvent(event.Payload.(engine.DiagEventPayload), true /*includePrefix:*/)
|
||||
msg := display.renderProgressDiagEvent(event.Payload().(engine.DiagEventPayload), true /*includePrefix:*/)
|
||||
if msg == "" {
|
||||
return
|
||||
}
|
||||
case engine.StdoutColorEvent:
|
||||
display.handleSystemEvent(event.Payload.(engine.StdoutEventPayload))
|
||||
display.handleSystemEvent(event.Payload().(engine.StdoutEventPayload))
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1012,15 +1010,12 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
|||
// what's going on, we can show them as ephemeral diagnostic messages that are
|
||||
// associated at the top level with the stack. That way if things are taking a while,
|
||||
// there's insight in the display as to what's going on.
|
||||
display.processNormalEvent(engine.Event{
|
||||
Type: engine.DiagEvent,
|
||||
Payload: engine.DiagEventPayload{
|
||||
Ephemeral: true,
|
||||
Severity: diag.Info,
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
Message: fmt.Sprintf("read %v %v", simplifyTypeName(eventUrn.Type()), eventUrn.Name()),
|
||||
},
|
||||
})
|
||||
display.processNormalEvent(engine.NewEvent(engine.DiagEvent, engine.DiagEventPayload{
|
||||
Ephemeral: true,
|
||||
Severity: diag.Info,
|
||||
Color: cmdutil.GetGlobalColorization(),
|
||||
Message: fmt.Sprintf("read %v %v", simplifyTypeName(eventUrn.Type()), eventUrn.Name()),
|
||||
}))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -1047,11 +1042,11 @@ func (display *ProgressDisplay) processNormalEvent(event engine.Event) {
|
|||
}
|
||||
|
||||
if event.Type == engine.ResourcePreEvent {
|
||||
step := event.Payload.(engine.ResourcePreEventPayload).Metadata
|
||||
step := event.Payload().(engine.ResourcePreEventPayload).Metadata
|
||||
row.SetStep(step)
|
||||
} else if event.Type == engine.ResourceOutputsEvent {
|
||||
isRefresh := display.getStepOp(row.Step()) == deploy.OpRefresh
|
||||
step := event.Payload.(engine.ResourceOutputsEventPayload).Metadata
|
||||
step := event.Payload().(engine.ResourceOutputsEventPayload).Metadata
|
||||
|
||||
// Is this the stack outputs event? If so, we'll need to print it out at the end of the plan.
|
||||
if step.URN == display.stackUrn {
|
||||
|
|
|
@ -57,7 +57,7 @@ func ShowQueryEvents(op string, events <-chan engine.Event,
|
|||
|
||||
out := os.Stdout
|
||||
if event.Type == engine.DiagEvent {
|
||||
payload := event.Payload.(engine.DiagEventPayload)
|
||||
payload := event.Payload().(engine.DiagEventPayload)
|
||||
if payload.Severity == diag.Error || payload.Severity == diag.Warning {
|
||||
out = os.Stderr
|
||||
}
|
||||
|
@ -81,11 +81,11 @@ func renderQueryEvent(event engine.Event, opts Options) string {
|
|||
return ""
|
||||
|
||||
case engine.StdoutColorEvent:
|
||||
return renderStdoutColorEvent(event.Payload.(engine.StdoutEventPayload), opts)
|
||||
return renderStdoutColorEvent(event.Payload().(engine.StdoutEventPayload), opts)
|
||||
|
||||
// Includes stdout of the query process.
|
||||
case engine.DiagEvent:
|
||||
return renderQueryDiagEvent(event.Payload.(engine.DiagEventPayload), opts)
|
||||
return renderQueryDiagEvent(event.Payload().(engine.DiagEventPayload), opts)
|
||||
|
||||
case engine.PreludeEvent, engine.SummaryEvent, engine.ResourceOperationFailed,
|
||||
engine.ResourceOutputsEvent, engine.ResourcePreEvent:
|
||||
|
|
|
@ -180,7 +180,7 @@ func (data *resourceRowData) DiagInfo() *DiagInfo {
|
|||
}
|
||||
|
||||
func (data *resourceRowData) RecordDiagEvent(event engine.Event) {
|
||||
payload := event.Payload.(engine.DiagEventPayload)
|
||||
payload := event.Payload().(engine.DiagEventPayload)
|
||||
data.recordDiagEventPayload(payload)
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ func (data *resourceRowData) PolicyPayloads() []engine.PolicyViolationEventPaylo
|
|||
|
||||
// RecordPolicyViolationEvent records a policy event with the resourceRowData.
|
||||
func (data *resourceRowData) RecordPolicyViolationEvent(event engine.Event) {
|
||||
pePayload := event.Payload.(engine.PolicyViolationEventPayload)
|
||||
pePayload := event.Payload().(engine.PolicyViolationEventPayload)
|
||||
data.policyPayloads = append(data.policyPayloads, pePayload)
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ func ShowWatchEvents(op string, action apitype.UpdateKind, events <-chan engine.
|
|||
continue
|
||||
case engine.DiagEvent:
|
||||
// Skip any ephemeral or debug messages, and elide all colorization.
|
||||
p := e.Payload.(engine.DiagEventPayload)
|
||||
p := e.Payload().(engine.DiagEventPayload)
|
||||
resourceName := ""
|
||||
if p.URN != "" {
|
||||
resourceName = string(p.URN.Name())
|
||||
|
@ -60,19 +60,19 @@ func ShowWatchEvents(op string, action apitype.UpdateKind, events <-chan engine.
|
|||
PrintfWithWatchPrefix(time.Now(), resourceName,
|
||||
"%s", renderDiffDiagEvent(p, opts))
|
||||
case engine.ResourcePreEvent:
|
||||
p := e.Payload.(engine.ResourcePreEventPayload)
|
||||
p := e.Payload().(engine.ResourcePreEventPayload)
|
||||
if shouldShow(p.Metadata, opts) {
|
||||
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
|
||||
"%s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
|
||||
}
|
||||
case engine.ResourceOutputsEvent:
|
||||
p := e.Payload.(engine.ResourceOutputsEventPayload)
|
||||
p := e.Payload().(engine.ResourceOutputsEventPayload)
|
||||
if shouldShow(p.Metadata, opts) {
|
||||
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
|
||||
"done %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
|
||||
}
|
||||
case engine.ResourceOperationFailed:
|
||||
p := e.Payload.(engine.ResourceOperationFailedPayload)
|
||||
p := e.Payload().(engine.ResourceOperationFailedPayload)
|
||||
if shouldShow(p.Metadata, opts) {
|
||||
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
|
||||
"failed %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
|
||||
|
|
|
@ -301,7 +301,7 @@ func (b *cloudBackend) getTarget(ctx context.Context, stackRef backend.StackRefe
|
|||
}
|
||||
|
||||
func isDebugDiagEvent(e engine.Event) bool {
|
||||
return e.Type == engine.DiagEvent && (e.Payload.(engine.DiagEventPayload)).Severity == diag.Debug
|
||||
return e.Type == engine.DiagEvent && (e.Payload().(engine.DiagEventPayload)).Severity == diag.Debug
|
||||
}
|
||||
|
||||
type engineEventBatch struct {
|
||||
|
|
|
@ -661,24 +661,18 @@ func (cancellationScopeSource) NewScope(events chan<- engine.Event, isPreview bo
|
|||
message += colors.BrightRed + "Note that terminating immediately may lead to orphaned resources " +
|
||||
"and other inconsistencies.\n" + colors.Reset
|
||||
}
|
||||
events <- engine.Event{
|
||||
Type: engine.StdoutColorEvent,
|
||||
Payload: engine.StdoutEventPayload{
|
||||
Message: message,
|
||||
Color: colors.Always,
|
||||
},
|
||||
}
|
||||
events <- engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{
|
||||
Message: message,
|
||||
Color: colors.Always,
|
||||
})
|
||||
|
||||
cancelSource.Cancel()
|
||||
} else {
|
||||
message := colors.BrightRed + "^C received; terminating" + colors.Reset
|
||||
events <- engine.Event{
|
||||
Type: engine.StdoutColorEvent,
|
||||
Payload: engine.StdoutEventPayload{
|
||||
Message: message,
|
||||
Color: colors.Always,
|
||||
},
|
||||
}
|
||||
events <- engine.NewEvent(engine.StdoutColorEvent, engine.StdoutEventPayload{
|
||||
Message: message,
|
||||
Color: colors.Always,
|
||||
})
|
||||
|
||||
cancelSource.Terminate()
|
||||
}
|
||||
|
|
|
@ -232,25 +232,32 @@ func PrintObject(
|
|||
}
|
||||
}
|
||||
|
||||
func massageStackPreviewAdd(p resource.PropertyValue) {
|
||||
func massageStackPreviewAdd(p resource.PropertyValue) resource.PropertyValue {
|
||||
switch {
|
||||
case p.IsArray():
|
||||
for _, v := range p.ArrayValue() {
|
||||
massageStackPreviewAdd(v)
|
||||
arr := make([]resource.PropertyValue, len(p.ArrayValue()))
|
||||
for i, v := range p.ArrayValue() {
|
||||
arr[i] = massageStackPreviewAdd(v)
|
||||
}
|
||||
return resource.NewArrayProperty(arr)
|
||||
case p.IsObject():
|
||||
delete(p.ObjectValue(), "@isPulumiResource")
|
||||
for _, v := range p.ObjectValue() {
|
||||
massageStackPreviewAdd(v)
|
||||
obj := resource.PropertyMap{}
|
||||
for k, v := range p.ObjectValue() {
|
||||
if k != "@isPulumiResource" {
|
||||
obj[k] = massageStackPreviewAdd(v)
|
||||
}
|
||||
}
|
||||
return resource.NewObjectProperty(obj)
|
||||
default:
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
func massageStackPreviewDiff(diff resource.ValueDiff, inResource bool) {
|
||||
switch {
|
||||
case diff.Array != nil:
|
||||
for _, p := range diff.Array.Adds {
|
||||
massageStackPreviewAdd(p)
|
||||
for i, p := range diff.Array.Adds {
|
||||
diff.Array.Adds[i] = massageStackPreviewAdd(p)
|
||||
}
|
||||
for _, d := range diff.Array.Updates {
|
||||
massageStackPreviewDiff(d, inResource)
|
||||
|
@ -278,8 +285,8 @@ func massageStackPreviewOutputDiff(diff *resource.ObjectDiff, inResource bool) {
|
|||
}
|
||||
}
|
||||
|
||||
for _, p := range diff.Adds {
|
||||
massageStackPreviewAdd(p)
|
||||
for i, p := range diff.Adds {
|
||||
diff.Adds[i] = massageStackPreviewAdd(p)
|
||||
}
|
||||
for k, d := range diff.Updates {
|
||||
if isResource && d.New.IsComputed() && !shouldPrintPropertyValue(d.Old, false) {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/pulumi/pulumi/sdk/v2/go/common/resource/plugin"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/tokens"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/util/contract"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/util/deepcopy"
|
||||
"github.com/pulumi/pulumi/sdk/v2/go/common/util/logging"
|
||||
)
|
||||
|
||||
|
@ -35,7 +36,38 @@ import (
|
|||
// type for the `Payload` field will differ depending on the value of the `Type` field
|
||||
type Event struct {
|
||||
Type EventType
|
||||
Payload interface{}
|
||||
payload interface{}
|
||||
}
|
||||
|
||||
func NewEvent(typ EventType, payload interface{}) Event {
|
||||
ok := false
|
||||
switch typ {
|
||||
case CancelEvent:
|
||||
ok = payload == nil
|
||||
case StdoutColorEvent:
|
||||
_, ok = payload.(StdoutEventPayload)
|
||||
case DiagEvent:
|
||||
_, ok = payload.(DiagEventPayload)
|
||||
case PreludeEvent:
|
||||
_, ok = payload.(PreludeEventPayload)
|
||||
case SummaryEvent:
|
||||
_, ok = payload.(SummaryEventPayload)
|
||||
case ResourcePreEvent:
|
||||
_, ok = payload.(ResourcePreEventPayload)
|
||||
case ResourceOutputsEvent:
|
||||
_, ok = payload.(ResourceOutputsEventPayload)
|
||||
case ResourceOperationFailed:
|
||||
_, ok = payload.(ResourceOperationFailedPayload)
|
||||
case PolicyViolationEvent:
|
||||
_, ok = payload.(PolicyViolationEventPayload)
|
||||
default:
|
||||
contract.Failf("unknown event type %v", typ)
|
||||
}
|
||||
contract.Assertf(ok, "invalid payload of type %T for event type %v", payload, typ)
|
||||
return Event{
|
||||
Type: typ,
|
||||
payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
// EventType is the kind of event being emitted.
|
||||
|
@ -53,6 +85,10 @@ const (
|
|||
PolicyViolationEvent EventType = "policy-violation"
|
||||
)
|
||||
|
||||
func (e Event) Payload() interface{} {
|
||||
return deepcopy.Copy(e.payload)
|
||||
}
|
||||
|
||||
func cancelEvent() Event {
|
||||
return Event{Type: CancelEvent}
|
||||
}
|
||||
|
@ -429,27 +465,21 @@ func (e *eventEmitter) resourceOperationFailedEvent(
|
|||
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: ResourceOperationFailed,
|
||||
Payload: ResourceOperationFailedPayload{
|
||||
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
||||
Status: status,
|
||||
Steps: steps,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(ResourceOperationFailed, ResourceOperationFailedPayload{
|
||||
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
||||
Status: status,
|
||||
Steps: steps,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) resourceOutputsEvent(op deploy.StepOp, step deploy.Step, planning bool, debug bool) {
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: ResourceOutputsEvent,
|
||||
Payload: ResourceOutputsEventPayload{
|
||||
Metadata: makeStepEventMetadata(op, step, debug),
|
||||
Planning: planning,
|
||||
Debug: debug,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(ResourceOutputsEvent, ResourceOutputsEventPayload{
|
||||
Metadata: makeStepEventMetadata(op, step, debug),
|
||||
Planning: planning,
|
||||
Debug: debug,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) resourcePreEvent(
|
||||
|
@ -457,14 +487,11 @@ func (e *eventEmitter) resourcePreEvent(
|
|||
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: ResourcePreEvent,
|
||||
Payload: ResourcePreEventPayload{
|
||||
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
||||
Planning: planning,
|
||||
Debug: debug,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(ResourcePreEvent, ResourcePreEventPayload{
|
||||
Metadata: makeStepEventMetadata(step.Op(), step, debug),
|
||||
Planning: planning,
|
||||
Debug: debug,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
|
||||
|
@ -478,44 +505,35 @@ func (e *eventEmitter) preludeEvent(isPreview bool, cfg config.Map) {
|
|||
configStringMap[keyString] = valueString
|
||||
}
|
||||
|
||||
e.ch <- Event{
|
||||
Type: PreludeEvent,
|
||||
Payload: PreludeEventPayload{
|
||||
IsPreview: isPreview,
|
||||
Config: configStringMap,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(PreludeEvent, PreludeEventPayload{
|
||||
IsPreview: isPreview,
|
||||
Config: configStringMap,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) previewSummaryEvent(resourceChanges ResourceChanges, policyPacks map[string]string) {
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: SummaryEvent,
|
||||
Payload: SummaryEventPayload{
|
||||
IsPreview: true,
|
||||
MaybeCorrupt: false,
|
||||
Duration: 0,
|
||||
ResourceChanges: resourceChanges,
|
||||
PolicyPacks: policyPacks,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(SummaryEvent, SummaryEventPayload{
|
||||
IsPreview: true,
|
||||
MaybeCorrupt: false,
|
||||
Duration: 0,
|
||||
ResourceChanges: resourceChanges,
|
||||
PolicyPacks: policyPacks,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) updateSummaryEvent(maybeCorrupt bool,
|
||||
duration time.Duration, resourceChanges ResourceChanges, policyPacks map[string]string) {
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: SummaryEvent,
|
||||
Payload: SummaryEventPayload{
|
||||
IsPreview: false,
|
||||
MaybeCorrupt: maybeCorrupt,
|
||||
Duration: duration,
|
||||
ResourceChanges: resourceChanges,
|
||||
PolicyPacks: policyPacks,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(SummaryEvent, SummaryEventPayload{
|
||||
IsPreview: false,
|
||||
MaybeCorrupt: maybeCorrupt,
|
||||
Duration: duration,
|
||||
ResourceChanges: resourceChanges,
|
||||
PolicyPacks: policyPacks,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDiagnostic) {
|
||||
|
@ -546,37 +564,31 @@ func (e *eventEmitter) policyViolationEvent(urn resource.URN, d plugin.AnalyzeDi
|
|||
buffer.WriteString(colors.Reset)
|
||||
buffer.WriteRune('\n')
|
||||
|
||||
e.ch <- Event{
|
||||
Type: PolicyViolationEvent,
|
||||
Payload: PolicyViolationEventPayload{
|
||||
ResourceURN: urn,
|
||||
Message: logging.FilterString(buffer.String()),
|
||||
Color: colors.Raw,
|
||||
PolicyName: d.PolicyName,
|
||||
PolicyPackName: d.PolicyPackName,
|
||||
PolicyPackVersion: d.PolicyPackVersion,
|
||||
EnforcementLevel: d.EnforcementLevel,
|
||||
Prefix: logging.FilterString(prefix.String()),
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(PolicyViolationEvent, PolicyViolationEventPayload{
|
||||
ResourceURN: urn,
|
||||
Message: logging.FilterString(buffer.String()),
|
||||
Color: colors.Raw,
|
||||
PolicyName: d.PolicyName,
|
||||
PolicyPackName: d.PolicyPackName,
|
||||
PolicyPackVersion: d.PolicyPackVersion,
|
||||
EnforcementLevel: d.EnforcementLevel,
|
||||
Prefix: logging.FilterString(prefix.String()),
|
||||
})
|
||||
}
|
||||
|
||||
func diagEvent(e *eventEmitter, d *diag.Diag, prefix, msg string, sev diag.Severity,
|
||||
ephemeral bool) {
|
||||
contract.Requiref(e != nil, "e", "!= nil")
|
||||
|
||||
e.ch <- Event{
|
||||
Type: DiagEvent,
|
||||
Payload: DiagEventPayload{
|
||||
URN: d.URN,
|
||||
Prefix: logging.FilterString(prefix),
|
||||
Message: logging.FilterString(msg),
|
||||
Color: colors.Raw,
|
||||
Severity: sev,
|
||||
StreamID: d.StreamID,
|
||||
Ephemeral: ephemeral,
|
||||
},
|
||||
}
|
||||
e.ch <- NewEvent(DiagEvent, DiagEventPayload{
|
||||
URN: d.URN,
|
||||
Prefix: logging.FilterString(prefix),
|
||||
Message: logging.FilterString(msg),
|
||||
Color: colors.Raw,
|
||||
Severity: sev,
|
||||
StreamID: d.StreamID,
|
||||
Ephemeral: ephemeral,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *eventEmitter) diagDebugEvent(d *diag.Diag, prefix, msg string, ephemeral bool) {
|
||||
|
|
|
@ -1050,7 +1050,7 @@ func TestSingleResourceDiffUnavailable(t *testing.T) {
|
|||
found := false
|
||||
for _, e := range events {
|
||||
if e.Type == DiagEvent {
|
||||
p := e.Payload.(DiagEventPayload)
|
||||
p := e.Payload().(DiagEventPayload)
|
||||
if p.URN == resURN && p.Severity == diag.Warning && p.Message == "diff unavailable" {
|
||||
found = true
|
||||
break
|
||||
|
@ -1457,7 +1457,7 @@ func TestCheckFailureRecord(t *testing.T) {
|
|||
sawFailure := false
|
||||
for _, evt := range evts {
|
||||
if evt.Type == DiagEvent {
|
||||
e := evt.Payload.(DiagEventPayload)
|
||||
e := evt.Payload().(DiagEventPayload)
|
||||
msg := colors.Never.Colorize(e.Message)
|
||||
sawFailure = msg == "oh no, check had an error\n" && e.Severity == diag.Error
|
||||
}
|
||||
|
@ -1507,7 +1507,7 @@ func TestCheckFailureInvalidPropertyRecord(t *testing.T) {
|
|||
sawFailure := false
|
||||
for _, evt := range evts {
|
||||
if evt.Type == DiagEvent {
|
||||
e := evt.Payload.(DiagEventPayload)
|
||||
e := evt.Payload().(DiagEventPayload)
|
||||
msg := colors.Never.Colorize(e.Message)
|
||||
sawFailure = strings.Contains(msg, "field is not valid") && e.Severity == diag.Error
|
||||
if sawFailure {
|
||||
|
@ -2090,7 +2090,7 @@ func TestLanguageHostDiagnostics(t *testing.T) {
|
|||
sawExitCode := false
|
||||
for _, evt := range evts {
|
||||
if evt.Type == DiagEvent {
|
||||
e := evt.Payload.(DiagEventPayload)
|
||||
e := evt.Payload().(DiagEventPayload)
|
||||
msg := colors.Never.Colorize(e.Message)
|
||||
sawExitCode = strings.Contains(msg, errorText) && e.Severity == diag.Error
|
||||
if sawExitCode {
|
||||
|
@ -3159,7 +3159,7 @@ func TestSingleResourceIgnoreChanges(t *testing.T) {
|
|||
events []Event, res result.Result) result.Result {
|
||||
for _, event := range events {
|
||||
if event.Type == ResourcePreEvent {
|
||||
payload := event.Payload.(ResourcePreEventPayload)
|
||||
payload := event.Payload().(ResourcePreEventPayload)
|
||||
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
||||
}
|
||||
}
|
||||
|
@ -3494,7 +3494,7 @@ func TestAliases(t *testing.T) {
|
|||
events []Event, res result.Result) result.Result {
|
||||
for _, event := range events {
|
||||
if event.Type == ResourcePreEvent {
|
||||
payload := event.Payload.(ResourcePreEventPayload)
|
||||
payload := event.Payload().(ResourcePreEventPayload)
|
||||
assert.Subset(t, allowedOps, []deploy.StepOp{payload.Metadata.Op})
|
||||
}
|
||||
}
|
||||
|
@ -3803,7 +3803,7 @@ func TestPersistentDiff(t *testing.T) {
|
|||
found := false
|
||||
for _, e := range events {
|
||||
if e.Type == ResourcePreEvent {
|
||||
p := e.Payload.(ResourcePreEventPayload).Metadata
|
||||
p := e.Payload().(ResourcePreEventPayload).Metadata
|
||||
if p.URN == resURN {
|
||||
assert.Equal(t, deploy.OpUpdate, p.Op)
|
||||
found = true
|
||||
|
@ -3824,7 +3824,7 @@ func TestPersistentDiff(t *testing.T) {
|
|||
found := false
|
||||
for _, e := range events {
|
||||
if e.Type == ResourcePreEvent {
|
||||
p := e.Payload.(ResourcePreEventPayload).Metadata
|
||||
p := e.Payload().(ResourcePreEventPayload).Metadata
|
||||
if p.URN == resURN {
|
||||
assert.Equal(t, deploy.OpSame, p.Op)
|
||||
found = true
|
||||
|
@ -3884,7 +3884,7 @@ func TestDetailedDiffReplace(t *testing.T) {
|
|||
found := false
|
||||
for _, e := range events {
|
||||
if e.Type == ResourcePreEvent {
|
||||
p := e.Payload.(ResourcePreEventPayload).Metadata
|
||||
p := e.Payload().(ResourcePreEventPayload).Metadata
|
||||
if p.URN == resURN && p.Op == deploy.OpReplace {
|
||||
found = true
|
||||
}
|
||||
|
@ -5566,7 +5566,7 @@ func TestIgnoreChangesGolangLifecycle(t *testing.T) {
|
|||
events []Event, res result.Result) result.Result {
|
||||
for _, event := range events {
|
||||
if event.Type == ResourcePreEvent {
|
||||
payload := event.Payload.(ResourcePreEventPayload)
|
||||
payload := event.Payload().(ResourcePreEventPayload)
|
||||
assert.Equal(t, []deploy.StepOp{deploy.OpCreate}, []deploy.StepOp{payload.Metadata.Op})
|
||||
}
|
||||
}
|
||||
|
|
102
sdk/go/common/util/deepcopy/copy.go
Normal file
102
sdk/go/common/util/deepcopy/copy.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package deepcopy
|
||||
|
||||
import "reflect"
|
||||
|
||||
// Copy returns a deep copy of the provided value.
|
||||
//
|
||||
// If there are multiple references to the same value inside the provided value, the multiply-referenced value will be
|
||||
// copied multiple times.
|
||||
func Copy(i interface{}) interface{} {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
return copy(reflect.ValueOf(i)).Interface()
|
||||
}
|
||||
|
||||
func copy(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() {
|
||||
return v
|
||||
}
|
||||
|
||||
typ := v.Type()
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool,
|
||||
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64,
|
||||
reflect.Complex64, reflect.Complex128,
|
||||
reflect.String,
|
||||
reflect.Func:
|
||||
// These all have value semantics. Return them as-is.
|
||||
return v
|
||||
case reflect.Chan:
|
||||
// Channels have referential semantics, but deep-copying them has no meaning. Return them as-is.
|
||||
return v
|
||||
case reflect.Interface:
|
||||
rv := reflect.New(typ).Elem()
|
||||
if !v.IsNil() {
|
||||
rv.Set(copy(v.Elem()))
|
||||
}
|
||||
return rv
|
||||
case reflect.Ptr:
|
||||
if v.IsNil() {
|
||||
return reflect.New(typ).Elem()
|
||||
}
|
||||
elem := copy(v.Elem())
|
||||
if elem.CanAddr() {
|
||||
return elem.Addr()
|
||||
}
|
||||
rv := reflect.New(typ.Elem())
|
||||
rv.Set(elem)
|
||||
return rv
|
||||
case reflect.Array:
|
||||
rv := reflect.New(typ).Elem()
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
rv.Index(i).Set(copy(v.Index(i)))
|
||||
}
|
||||
return rv
|
||||
case reflect.Slice:
|
||||
rv := reflect.New(typ).Elem()
|
||||
if !v.IsNil() {
|
||||
rv.Set(reflect.MakeSlice(typ, v.Len(), v.Cap()))
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
rv.Index(i).Set(copy(v.Index(i)))
|
||||
}
|
||||
}
|
||||
return rv
|
||||
case reflect.Map:
|
||||
rv := reflect.New(typ).Elem()
|
||||
if !v.IsNil() {
|
||||
rv.Set(reflect.MakeMap(typ))
|
||||
iter := v.MapRange()
|
||||
for iter.Next() {
|
||||
rv.SetMapIndex(copy(iter.Key()), copy(iter.Value()))
|
||||
}
|
||||
}
|
||||
return rv
|
||||
case reflect.Struct:
|
||||
rv := reflect.New(typ).Elem()
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
if f := rv.Field(i); f.CanSet() {
|
||||
f.Set(copy(v.Field(i)))
|
||||
}
|
||||
}
|
||||
return rv
|
||||
default:
|
||||
panic("unexpected kind " + typ.Kind().String())
|
||||
}
|
||||
}
|
80
sdk/go/common/util/deepcopy/copy_test.go
Normal file
80
sdk/go/common/util/deepcopy/copy_test.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright 2016-2020, Pulumi Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package deepcopy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
cases := []interface{}{
|
||||
bool(false),
|
||||
bool(true),
|
||||
int(-42),
|
||||
int8(-42),
|
||||
int16(-42),
|
||||
int32(-42),
|
||||
int64(-42),
|
||||
uint(42),
|
||||
uint8(42),
|
||||
uint16(42),
|
||||
uint32(42),
|
||||
uint64(42),
|
||||
float32(3.14159),
|
||||
float64(3.14159),
|
||||
complex64(complex(3.14159, -42)),
|
||||
complex(3.14159, -42),
|
||||
"foo",
|
||||
[2]byte{42, 24},
|
||||
[]byte{0, 1, 2, 3},
|
||||
[]string{"foo", "bar"},
|
||||
map[string]int{
|
||||
"a": 42,
|
||||
"b": 24,
|
||||
},
|
||||
struct {
|
||||
Foo int
|
||||
Bar map[int]int
|
||||
}{
|
||||
Foo: 42,
|
||||
Bar: map[int]int{
|
||||
19: 77,
|
||||
},
|
||||
},
|
||||
[]map[string]string{
|
||||
{
|
||||
"foo": "bar",
|
||||
"baz": "qux",
|
||||
},
|
||||
{
|
||||
"alpha": "beta",
|
||||
},
|
||||
},
|
||||
map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "baz",
|
||||
},
|
||||
"bar": []int{42},
|
||||
},
|
||||
}
|
||||
for i, c := range cases {
|
||||
t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) {
|
||||
assert.EqualValues(t, c, Copy(c))
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue