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:
Pat Gavlin 2020-07-16 23:52:31 -07:00 committed by GitHub
parent 67525ee4b1
commit 418e2291a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 370 additions and 179 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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:

View file

@ -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)
}

View file

@ -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())

View file

@ -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 {

View file

@ -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()
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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})
}
}

View 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())
}
}

View 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))
})
}
}