Simplify previews, initialize plugin events

This changes two things:

1) Eliminates the fact that we had two kinds of previews in our engine.

2) Always initialize the plugin.Events, to ensure that all plugin loads
   are persisted no matter the update type (update, refresh, destroy),
   and skip initializing it when dryRun == true, since we won't save them.
This commit is contained in:
joeduffy 2018-05-18 14:58:06 -07:00
parent 4e9b228089
commit 614a2cdeb3
5 changed files with 73 additions and 133 deletions

View file

@ -873,13 +873,9 @@ func (b *cloudBackend) runEngineAction(
switch action {
case client.UpdateKindPreview:
changes, err = engine.Preview(u, engineCtx, opts.Engine)
changes, err = engine.Update(u, engineCtx, opts.Engine, true)
case client.UpdateKindUpdate:
if dryRun {
changes, err = engine.Preview(u, engineCtx, opts.Engine)
} else {
changes, err = engine.Update(u, engineCtx, opts.Engine, dryRun)
}
changes, err = engine.Update(u, engineCtx, opts.Engine, dryRun)
case client.UpdateKindRefresh:
changes, err = engine.Refresh(u, engineCtx, opts.Engine, dryRun)
case client.UpdateKindDestroy:

View file

@ -194,15 +194,8 @@ func (b *localBackend) GetLatestConfiguration(ctx context.Context,
func (b *localBackend) Preview(
_ context.Context, stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) (engine.ResourceChanges, error) {
return b.performEngineOp("previewing", backend.PreviewUpdate,
stackRef.StackName(), proj, root, m, opts, scopes,
func(u engine.UpdateInfo, ctx *engine.Context,
opts engine.UpdateOptions, dryRun bool) (engine.ResourceChanges, error) {
contract.Assert(dryRun)
return engine.Preview(u, ctx, opts)
},
)
stackRef.StackName(), proj, root, m, opts, scopes, engine.Update)
}
func (b *localBackend) Update(
@ -227,7 +220,6 @@ func (b *localBackend) Update(
func (b *localBackend) Refresh(
_ context.Context, stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) (engine.ResourceChanges, error) {
return b.performEngineOp("refreshing", backend.RefreshUpdate,
stackRef.StackName(), proj, root, m, opts, scopes, engine.Refresh)
}
@ -235,7 +227,6 @@ func (b *localBackend) Refresh(
func (b *localBackend) Destroy(
_ context.Context, stackRef backend.StackReference, proj *workspace.Project, root string, m backend.UpdateMetadata,
opts backend.UpdateOptions, scopes backend.CancellationScopeSource) (engine.ResourceChanges, error) {
return b.performEngineOp("destroying", backend.DestroyUpdate,
stackRef.StackName(), proj, root, m, opts, scopes, engine.Destroy)
}

View file

@ -75,11 +75,10 @@ type planOptions struct {
// creates resources to compare against the current checkpoint state (e.g., by evaluating a program, etc).
SourceFunc planSourceFunc
SkipOutputs bool // true if we we should skip printing outputs separately.
DOT bool // true if we should print the DOT file for this plan.
Events eventEmitter // the channel to write events from the engine to.
Diag diag.Sink // the sink to use for diag'ing.
PluginEvents plugin.Events // an optional listener for plugin events
SkipOutputs bool // true if we we should skip printing outputs separately.
DOT bool // true if we should print the DOT file for this plan.
Events eventEmitter // the channel to write events from the engine to.
Diag diag.Sink // the sink to use for diag'ing.
}
// planSourceFunc is a callback that will be used to prepare for, and evaluate, the "new" state for a stack.
@ -88,18 +87,25 @@ type planSourceFunc func(
target *deploy.Target, plugctx *plugin.Context, dryRun bool) (deploy.Source, error)
// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan.
func plan(ctx *planContext, opts planOptions, dryRun bool) (*planResult, error) {
contract.Assert(ctx != nil)
contract.Assert(ctx.Update != nil)
func plan(ctx *Context, info *planContext, opts planOptions, dryRun bool) (*planResult, error) {
contract.Assert(info != nil)
contract.Assert(info.Update != nil)
contract.Assert(opts.SourceFunc != nil)
// If this isn't a dry run, we will need to record plugin events, so that we persist them in the checkpoint. If
// we're just doing a dry run, we don't actually need to persist anything (and indeed trying to do so would fail).
var pluginEvents plugin.Events
if !dryRun {
pluginEvents = &pluginActions{ctx}
}
// First, load the package metadata and the deployment target in preparation for executing the package's program
// and creating resources. This includes fetching its pwd and main overrides.
proj, target := ctx.Update.GetProject(), ctx.Update.GetTarget()
proj, target := info.Update.GetProject(), info.Update.GetTarget()
contract.Assert(proj != nil)
contract.Assert(target != nil)
projinfo := &Projinfo{Proj: proj, Root: ctx.Update.GetRoot()}
pwd, main, plugctx, err := ProjectInfoContext(projinfo, target, opts.PluginEvents, opts.Diag, ctx.TracingSpan)
projinfo := &Projinfo{Proj: proj, Root: info.Update.GetRoot()}
pwd, main, plugctx, err := ProjectInfoContext(projinfo, target, pluginEvents, opts.Diag, info.TracingSpan)
if err != nil {
return nil, err
}
@ -127,7 +133,7 @@ func plan(ctx *planContext, opts planOptions, dryRun bool) (*planResult, error)
// Generate a plan; this API handles all interesting cases (create, update, delete).
plan := deploy.NewPlan(plugctx, target, target.Snapshot, source, analyzers, dryRun)
return &planResult{
Ctx: ctx,
Ctx: info,
Plugctx: plugctx,
Plan: plan,
Options: opts,
@ -242,7 +248,7 @@ func printPlan(ctx *Context, result *planResult, dryRun bool) (ResourceChanges,
result.Options.Events.preludeEvent(dryRun, result.Ctx.Update.GetTarget().Config)
// Walk the plan's steps and and pretty-print them out.
actions := newPreviewActions(result.Options)
actions := newPlanActions(result.Options)
_, step, _, err := result.Walk(ctx, actions, true)
if err != nil {
var failedUrn resource.URN
@ -260,6 +266,56 @@ func printPlan(ctx *Context, result *planResult, dryRun bool) (ResourceChanges,
return changes, nil
}
type planActions struct {
Refresh bool
Ops map[deploy.StepOp]int
Opts planOptions
Seen map[resource.URN]deploy.Step
}
func newPlanActions(opts planOptions) *planActions {
return &planActions{
Ops: make(map[deploy.StepOp]int),
Opts: opts,
Seen: make(map[resource.URN]deploy.Step),
}
}
func (acts *planActions) OnResourceStepPre(step deploy.Step) (interface{}, error) {
acts.Seen[step.URN()] = step
acts.Opts.Events.resourcePreEvent(step, true /*planning*/, acts.Opts.Debug)
return nil, nil
}
func (acts *planActions) OnResourceStepPost(ctx interface{},
step deploy.Step, status resource.Status, err error) error {
assertSeen(acts.Seen, step)
if err != nil {
acts.Opts.Diag.Errorf(diag.GetPreviewFailedError(step.URN()), err)
} else {
// Track the operation if shown and/or if it is a logically meaningful operation.
if step.Logical() {
acts.Ops[step.Op()]++
}
_ = acts.OnResourceOutputs(step)
}
return nil
}
func (acts *planActions) OnResourceOutputs(step deploy.Step) error {
assertSeen(acts.Seen, step)
// Print the resource outputs separately, unless this is a refresh in which case they are already printed.
if !acts.Opts.SkipOutputs {
acts.Opts.Events.resourceOutputsEvent(step, true /*planning*/, acts.Opts.Debug)
}
return nil
}
func assertSeen(seen map[resource.URN]deploy.Step, step deploy.Step) {
_, has := seen[step.URN()]
contract.Assertf(has, "URN '%v' had not been marked as seen", step.URN())

View file

@ -1,102 +0,0 @@
// Copyright 2018, Pulumi Corporation. All rights reserved.
package engine
import (
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func Preview(u UpdateInfo, ctx *Context, opts UpdateOptions) (ResourceChanges, error) {
contract.Require(u != nil, "u")
contract.Require(ctx != nil, "ctx")
defer func() { ctx.Events <- cancelEvent() }()
info, err := newPlanContext(u, "preview", ctx.ParentSpan)
if err != nil {
return nil, err
}
defer info.Close()
emitter := makeEventEmitter(ctx.Events, u)
return preview(ctx, info, planOptions{
UpdateOptions: opts,
SourceFunc: newUpdateSource,
Events: emitter,
Diag: newEventSink(emitter),
})
}
func preview(ctx *Context, info *planContext, opts planOptions) (ResourceChanges, error) {
result, err := plan(info, opts, true /*dryRun*/)
if err != nil {
return nil, err
}
if result != nil {
defer contract.IgnoreClose(result)
// Make the current working directory the same as the program's, and restore it upon exit.
done, err := result.Chdir()
if err != nil {
return nil, err
}
defer done()
return printPlan(ctx, result, true /*dryRun*/)
}
return nil, nil
}
type previewActions struct {
Refresh bool
Ops map[deploy.StepOp]int
Opts planOptions
Seen map[resource.URN]deploy.Step
}
func newPreviewActions(opts planOptions) *previewActions {
return &previewActions{
Ops: make(map[deploy.StepOp]int),
Opts: opts,
Seen: make(map[resource.URN]deploy.Step),
}
}
func (acts *previewActions) OnResourceStepPre(step deploy.Step) (interface{}, error) {
acts.Seen[step.URN()] = step
acts.Opts.Events.resourcePreEvent(step, true /*planning*/, acts.Opts.Debug)
return nil, nil
}
func (acts *previewActions) OnResourceStepPost(ctx interface{},
step deploy.Step, status resource.Status, err error) error {
assertSeen(acts.Seen, step)
if err != nil {
acts.Opts.Diag.Errorf(diag.GetPreviewFailedError(step.URN()), err)
} else {
// Track the operation if shown and/or if it is a logically meaningful operation.
if step.Logical() {
acts.Ops[step.Op()]++
}
_ = acts.OnResourceOutputs(step)
}
return nil
}
func (acts *previewActions) OnResourceOutputs(step deploy.Step) error {
assertSeen(acts.Seen, step)
// Print the resource outputs separately, unless this is a refresh in which case they are already printed.
if !acts.Opts.SkipOutputs {
acts.Opts.Events.resourceOutputsEvent(step, true /*planning*/, acts.Opts.Debug)
}
return nil
}

View file

@ -57,7 +57,6 @@ func Update(u UpdateInfo, ctx *Context, opts UpdateOptions, dryRun bool) (Resour
SourceFunc: newUpdateSource,
Events: emitter,
Diag: newEventSink(emitter),
PluginEvents: &pluginActions{ctx},
}, dryRun)
}
@ -91,7 +90,7 @@ func newUpdateSource(
}
func update(ctx *Context, info *planContext, opts planOptions, dryRun bool) (ResourceChanges, error) {
result, err := plan(info, opts, dryRun)
result, err := plan(ctx, info, opts, dryRun)
if err != nil {
return nil, err
}