pulumi/pkg/engine/preview.go
joeduffy c5b7b6ef11 Bring back component outputs
This change brings back component outputs to the overall system again.
In doing so, it generally overhauls the way we do resource RPCs a bit:

* Instead of RegisterResource and CompleteResource, we call these
  BeginRegisterResource and EndRegisterResource, which begins to model
  these as effectively "asynchronous" resource requests.  This should also
  help with parallelism (https://github.com/pulumi/pulumi/issues/106).

* Flip the CLI/engine a little on its head.  Rather than it driving the
  planning and deployment process, we move more to a model where it
  simply observes it.  This is done by implementing an event handler
  interface with three events: OnResourceStepPre, OnResourceStepPost,
  and OnResourceComplete.  The first two are invoked immediately before
  and after any step operation, and the latter is invoked whenever a
  EndRegisterResource comes in.  The reason for the asymmetry here is
  that the checkpointing logic in the deployment engine is largely
  untouched (intentionally, as this is a sensitive part of the system),
  and so the "begin"/"end" nature doesn't flow through faithfully.

* Also make the engine more event-oriented in its terminology and the
  way it handles the incoming BeginRegisterResource and
  EndRegisterResource events from the language host.  This is the first
  step down a long road of incrementally refactoring the engine to work
  this way, a necessary prerequisite for parallelism.
2017-11-29 07:42:14 -08:00

117 lines
3.8 KiB
Go

// Copyright 2017, Pulumi Corporation. All rights reserved.
package engine
import (
"bytes"
"github.com/pkg/errors"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/resource"
"github.com/pulumi/pulumi/pkg/resource/deploy"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type PreviewOptions struct {
Package string // the package to compute the preview for
Analyzers []string // an optional set of analyzers to run as part of this deployment.
Parallel int // the degree of parallelism for resource operations (<=1 for serial).
ShowConfig bool // true to show the configuration variables being used.
ShowReplacementSteps bool // true to show the replacement steps in the plan.
ShowSames bool // true to show the resources that aren't updated, in addition to those that are.
Summary bool // true if we should only summarize resources and operations.
}
func (eng *Engine) Preview(stack tokens.QName, events chan<- Event, opts PreviewOptions) error {
contract.Require(stack != tokens.QName(""), "stack")
contract.Require(events != nil, "events")
defer func() { events <- cancelEvent() }()
info, err := eng.planContextFromStack(stack, opts.Package)
if err != nil {
return err
}
defer info.Close()
return eng.previewLatest(info, deployOptions{
Destroy: false,
DryRun: true,
Analyzers: opts.Analyzers,
Parallel: opts.Parallel,
ShowConfig: opts.ShowConfig,
ShowReplacementSteps: opts.ShowReplacementSteps,
ShowSames: opts.ShowSames,
Summary: opts.Summary,
Events: events,
Diag: newEventSink(events, diag.FormatOptions{
Colors: true,
}),
})
}
func (eng *Engine) previewLatest(info *planContext, opts deployOptions) error {
result, err := eng.plan(info, opts)
if err != nil {
return err
}
if result != nil {
defer contract.IgnoreClose(result)
if err := eng.printPlan(result); err != nil {
return err
}
}
if !opts.Diag.Success() {
// If any error occurred while walking the plan, be sure to let the developer know. Otherwise,
// although error messages may have spewed to the output, the final lines of the plan may look fine.
return errors.New("One or more errors occurred during the creation of this preview")
}
return nil
}
type previewActions struct {
Summary bytes.Buffer
Ops map[deploy.StepOp]int
Opts deployOptions
Seen map[resource.URN]deploy.Step
Shown map[resource.URN]bool
}
func newPreviewActions(opts deployOptions) *previewActions {
return &previewActions{
Ops: make(map[deploy.StepOp]int),
Opts: opts,
Seen: make(map[resource.URN]deploy.Step),
Shown: make(map[resource.URN]bool),
}
}
func (acts *previewActions) OnResourceStepPre(step deploy.Step) (interface{}, error) {
// Print this step information (resource and all its properties).
if shouldShow(acts.Seen, step, acts.Opts) {
printStep(&acts.Summary, step,
acts.Seen, acts.Shown, acts.Opts.Summary, acts.Opts.Detailed, true, 0 /*indent*/)
}
return nil, nil
}
func (acts *previewActions) OnResourceStepPost(ctx interface{},
step deploy.Step, status resource.Status, err error) error {
// We let `printPlan` handle error reporting for now.
if err == nil {
// Track the operation if shown and/or if it is a logically meaningful operation.
if step.Logical() {
acts.Ops[step.Op()]++
}
}
return nil
}
func (acts *previewActions) OnResourceComplete(step deploy.Step, state *deploy.FinalState) error {
// Print this step's output properties.
if shouldShow(acts.Seen, step, acts.Opts) && !acts.Opts.Summary {
printResourceOutputProperties(&acts.Summary, step, acts.Seen, acts.Shown, 0 /*indent*/)
}
return nil
}