2017-08-30 03:24:12 +02:00
|
|
|
// Copyright 2017, Pulumi Corporation. All rights reserved.
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
package engine
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2017-12-12 21:31:09 +01:00
|
|
|
"os"
|
2017-10-22 22:39:21 +02:00
|
|
|
"reflect"
|
2017-11-20 20:39:49 +01:00
|
|
|
"regexp"
|
2017-08-23 01:56:15 +02:00
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2018-02-19 19:58:03 +01:00
|
|
|
"github.com/opentracing/opentracing-go"
|
2018-03-13 00:27:39 +01:00
|
|
|
"github.com/sergi/go-diff/diffmatchpatch"
|
2018-02-19 19:58:03 +01:00
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
"github.com/pkg/errors"
|
2018-02-19 19:58:03 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/diag"
|
2017-09-22 04:18:21 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/diag/colors"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource/deploy"
|
|
|
|
"github.com/pulumi/pulumi/pkg/resource/plugin"
|
|
|
|
"github.com/pulumi/pulumi/pkg/tokens"
|
|
|
|
"github.com/pulumi/pulumi/pkg/util/contract"
|
2018-03-13 00:27:39 +01:00
|
|
|
"github.com/pulumi/pulumi/pkg/workspace"
|
2017-08-23 01:56:15 +02:00
|
|
|
)
|
|
|
|
|
2018-02-19 19:58:03 +01:00
|
|
|
// ProjectInfoContext returns information about the current project, including its pwd, main, and plugin context.
|
2018-03-07 01:09:42 +01:00
|
|
|
func ProjectInfoContext(projinfo *Projinfo, config plugin.ConfigSource, diag diag.Sink,
|
2018-02-19 19:58:03 +01:00
|
|
|
tracingSpan opentracing.Span) (string, string, *plugin.Context, error) {
|
|
|
|
contract.Require(projinfo != nil, "projinfo")
|
|
|
|
|
|
|
|
// If the package contains an override for the main entrypoint, use it.
|
|
|
|
pwd, main, err := projinfo.GetPwdMain()
|
|
|
|
if err != nil {
|
|
|
|
return "", "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a context for plugins.
|
2018-03-07 01:09:42 +01:00
|
|
|
ctx, err := plugin.NewContext(diag, nil, config, pwd, tracingSpan)
|
2018-02-19 19:58:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", "", nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return pwd, main, ctx, nil
|
|
|
|
}
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan.
|
2018-01-08 23:20:51 +01:00
|
|
|
func plan(info *planContext, opts deployOptions) (*planResult, error) {
|
2017-08-23 01:56:15 +02:00
|
|
|
contract.Assert(info != nil)
|
2018-01-08 22:01:40 +01:00
|
|
|
contract.Assert(info.Update != nil)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2018-01-08 22:01:40 +01:00
|
|
|
// First, load the package metadata and the deployment target in preparation for executing the package's program
|
2018-02-19 19:58:03 +01:00
|
|
|
// and creating resources. This includes fetching its pwd and main overrides.
|
2018-02-14 22:56:16 +01:00
|
|
|
proj, target := info.Update.GetProject(), info.Update.GetTarget()
|
|
|
|
contract.Assert(proj != nil)
|
2018-01-08 22:01:40 +01:00
|
|
|
contract.Assert(target != nil)
|
2018-02-14 22:56:16 +01:00
|
|
|
projinfo := &Projinfo{Proj: proj, Root: info.Update.GetRoot()}
|
2018-03-07 01:09:42 +01:00
|
|
|
pwd, main, ctx, err := ProjectInfoContext(projinfo, target, opts.Diag, info.TracingSpan)
|
2017-12-12 21:31:09 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-03-13 00:27:39 +01:00
|
|
|
// Figure out which plugins to load. In the case of a destroy, we consult the manifest for the plugin versions
|
|
|
|
// required to destroy it. Otherwise, we inspect the program contents to figure out which will be required.
|
|
|
|
var plugins []workspace.PluginInfo
|
|
|
|
if opts.Destroy {
|
|
|
|
if target.Snapshot != nil {
|
|
|
|
plugins = target.Snapshot.Manifest.Plugins
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if plugins, err = ctx.Host.GetRequiredPlugins(plugin.ProgInfo{
|
|
|
|
Proj: proj,
|
|
|
|
Pwd: pwd,
|
|
|
|
Program: main,
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-06 18:57:32 +01:00
|
|
|
// Now ensure that we have loaded up any plugins that the program will need in advance.
|
2018-03-13 00:27:39 +01:00
|
|
|
err = ctx.Host.EnsurePlugins(plugins)
|
2018-02-06 18:57:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// If that succeeded, create a new source that will perform interpretation of the compiled program.
|
2017-09-22 04:18:21 +02:00
|
|
|
// TODO[pulumi/pulumi#88]: we are passing `nil` as the arguments map; we need to allow a way to pass these.
|
2017-08-30 03:24:12 +02:00
|
|
|
source := deploy.NewEvalSource(ctx, &deploy.EvalRunInfo{
|
2018-02-19 19:58:03 +01:00
|
|
|
Proj: proj,
|
2017-11-16 16:49:07 +01:00
|
|
|
Pwd: pwd,
|
|
|
|
Program: main,
|
2018-01-08 22:01:40 +01:00
|
|
|
Target: target,
|
2017-08-30 03:24:12 +02:00
|
|
|
}, opts.Destroy, opts.DryRun)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
// If there are any analyzers in the project file, add them.
|
|
|
|
var analyzers []tokens.QName
|
2018-02-14 22:56:16 +01:00
|
|
|
if as := projinfo.Proj.Analyzers; as != nil {
|
2017-08-23 01:56:15 +02:00
|
|
|
for _, a := range *as {
|
|
|
|
analyzers = append(analyzers, a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append any analyzers from the command line.
|
|
|
|
for _, a := range opts.Analyzers {
|
|
|
|
analyzers = append(analyzers, tokens.QName(a))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a plan; this API handles all interesting cases (create, update, delete).
|
2018-01-10 03:40:08 +01:00
|
|
|
plan := deploy.NewPlan(ctx, target, target.Snapshot, source, analyzers, opts.DryRun)
|
2017-08-23 01:56:15 +02:00
|
|
|
return &planResult{
|
2017-10-05 23:08:46 +02:00
|
|
|
Ctx: ctx,
|
|
|
|
Info: info,
|
|
|
|
Plan: plan,
|
|
|
|
Options: opts,
|
2017-08-23 01:56:15 +02:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type planResult struct {
|
2017-09-17 17:10:46 +02:00
|
|
|
Ctx *plugin.Context // the context containing plugins and their state.
|
2017-10-03 02:27:55 +02:00
|
|
|
Info *planContext // plan command information.
|
2017-09-17 17:10:46 +02:00
|
|
|
Plan *deploy.Plan // the plan created by this command.
|
|
|
|
Options deployOptions // the deployment options.
|
|
|
|
}
|
|
|
|
|
2017-12-12 21:31:09 +01:00
|
|
|
// Chdir changes the directory so that all operations from now on are relative to the project we are working with.
|
|
|
|
// It returns a function that, when run, restores the old working directory.
|
|
|
|
func (res *planResult) Chdir() (func(), error) {
|
|
|
|
if res.Ctx.Pwd == "" {
|
|
|
|
return func() {}, nil
|
|
|
|
}
|
|
|
|
oldpwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err = os.Chdir(res.Ctx.Pwd); err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "could not change to the project working directory")
|
|
|
|
}
|
|
|
|
return func() {
|
|
|
|
// Restore the working directory after planning completes.
|
|
|
|
cderr := os.Chdir(oldpwd)
|
|
|
|
contract.IgnoreError(cderr)
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-10-02 23:26:51 +02:00
|
|
|
// Walk enumerates all steps in the plan, calling out to the provided action at each step. It returns four things: the
|
|
|
|
// resulting Snapshot, no matter whether an error occurs or not; an error, if something went wrong; the step that
|
|
|
|
// failed, if the error is non-nil; and finally the state of the resource modified in the failing step.
|
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 16:42:14 +01:00
|
|
|
func (res *planResult) Walk(events deploy.Events, preview bool) (deploy.PlanSummary,
|
|
|
|
deploy.Step, resource.Status, error) {
|
2017-09-17 17:10:46 +02:00
|
|
|
opts := deploy.Options{
|
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 16:42:14 +01:00
|
|
|
Events: events,
|
2017-09-17 17:10:46 +02:00
|
|
|
Parallel: res.Options.Parallel,
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2017-10-02 23:26:51 +02:00
|
|
|
// Fetch a plan iterator and keep walking it until we are done.
|
|
|
|
iter, err := res.Plan.Start(opts)
|
2017-08-23 01:56:15 +02:00
|
|
|
if err != nil {
|
2017-10-02 23:26:51 +02:00
|
|
|
return nil, nil, resource.StatusOK, err
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
step, err := iter.Next()
|
|
|
|
if err != nil {
|
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 16:42:14 +01:00
|
|
|
closeerr := iter.Close() // ignore close errors; the Next error trumps
|
|
|
|
contract.IgnoreError(closeerr)
|
2017-10-02 23:26:51 +02:00
|
|
|
return nil, nil, resource.StatusOK, err
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for step != nil {
|
2017-10-03 19:27:59 +02:00
|
|
|
// Perform any per-step actions.
|
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 16:42:14 +01:00
|
|
|
rst, err := iter.Apply(step, preview)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2017-10-02 23:26:51 +02:00
|
|
|
// If an error occurred, exit early.
|
|
|
|
if err != nil {
|
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 16:42:14 +01:00
|
|
|
closeerr := iter.Close() // ignore close errors; the action error trumps
|
|
|
|
contract.IgnoreError(closeerr)
|
2017-10-02 23:26:51 +02:00
|
|
|
return iter, step, rst, err
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-10-03 19:27:59 +02:00
|
|
|
contract.Assert(rst == resource.StatusOK)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2017-10-02 23:26:51 +02:00
|
|
|
step, err = iter.Next()
|
|
|
|
if err != nil {
|
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 16:42:14 +01:00
|
|
|
closeerr := iter.Close() // ignore close errors; the action error trumps
|
|
|
|
contract.IgnoreError(closeerr)
|
2017-10-02 23:26:51 +02:00
|
|
|
return iter, step, resource.StatusOK, err
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-10-02 23:26:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, return a summary and the resulting plan information.
|
|
|
|
return iter, nil, resource.StatusOK, iter.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (res *planResult) Close() error {
|
|
|
|
return res.Ctx.Close()
|
|
|
|
}
|
|
|
|
|
2018-01-20 21:15:28 +01:00
|
|
|
// printPlan prints the plan's result to the plan's Options.Events stream.
|
2018-01-25 03:22:41 +01:00
|
|
|
func printPlan(result *planResult) (ResourceChanges, error) {
|
2018-03-05 20:39:50 +01:00
|
|
|
result.Options.Events.preludeEvent(result.Options.DryRun,
|
|
|
|
result.Info.Update.GetTarget().Config)
|
2017-10-02 23:26:51 +02:00
|
|
|
|
2018-01-31 22:07:40 +01:00
|
|
|
// Walk the plan's steps and and pretty-print them out.
|
2017-11-17 03:21:41 +01:00
|
|
|
actions := newPreviewActions(result.Options)
|
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 16:42:14 +01:00
|
|
|
_, _, _, err := result.Walk(actions, true)
|
2017-10-02 23:26:51 +02:00
|
|
|
if err != nil {
|
2018-03-10 00:43:16 +01:00
|
|
|
return nil, errors.New("an error occurred while advancing the preview")
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2017-10-05 23:08:46 +02:00
|
|
|
if !result.Options.Diag.Success() {
|
Improve output formatting
This change improves our output formatting by generally adding
fewer prefixes. As shown in pulumi/pulumi#359, we were being
excessively verbose in many places, including prefixing every
console.out with "langhost[nodejs].stdout: ", displaying full
stack traces for simple errors like missing configuration, etc.
Overall, this change includes the following:
* Don't prefix stdout and stderr output from the program, other
than the standard "info:" prefix. I experimented with various
schemes here, but they all felt gratuitous. Simply emitting
the output seems fine, especially as it's closer to what would
happen if you just ran the program under node.
* Do NOT make writes to stderr fail the plan/deploy. Previously
we assumed that any console.errors, for instance, meant that
the overall program should fail. This simply isn't how stderr
is treated generally and meant you couldn't use certain
logging techniques and libraries, among other things.
* Do make sure that stderr writes in the program end up going to
stderr in the Pulumi CLI output, however, so that redirection
works as it should. This required a new Infoerr log level.
* Make a small fix to the planning logic so we don't attempt to
print the summary if an error occurs.
* Finally, add a new error type, RunError, that when thrown and
uncaught does not result in a full stack trace being printed.
Anyone can use this, however, we currently use it for config
errors so that we can terminate with a pretty error message,
rather than the monstrosity shown in pulumi/pulumi#359.
2017-09-23 14:20:11 +02:00
|
|
|
// 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.
|
2018-03-10 00:43:16 +01:00
|
|
|
return nil, errors.New("one or more errors occurred during this preview")
|
Improve output formatting
This change improves our output formatting by generally adding
fewer prefixes. As shown in pulumi/pulumi#359, we were being
excessively verbose in many places, including prefixing every
console.out with "langhost[nodejs].stdout: ", displaying full
stack traces for simple errors like missing configuration, etc.
Overall, this change includes the following:
* Don't prefix stdout and stderr output from the program, other
than the standard "info:" prefix. I experimented with various
schemes here, but they all felt gratuitous. Simply emitting
the output seems fine, especially as it's closer to what would
happen if you just ran the program under node.
* Do NOT make writes to stderr fail the plan/deploy. Previously
we assumed that any console.errors, for instance, meant that
the overall program should fail. This simply isn't how stderr
is treated generally and meant you couldn't use certain
logging techniques and libraries, among other things.
* Do make sure that stderr writes in the program end up going to
stderr in the Pulumi CLI output, however, so that redirection
works as it should. This required a new Infoerr log level.
* Make a small fix to the planning logic so we don't attempt to
print the summary if an error occurs.
* Finally, add a new error type, RunError, that when thrown and
uncaught does not result in a full stack trace being printed.
Anyone can use this, however, we currently use it for config
errors so that we can terminate with a pretty error message,
rather than the monstrosity shown in pulumi/pulumi#359.
2017-09-23 14:20:11 +02:00
|
|
|
}
|
|
|
|
|
2018-02-02 06:15:09 +01:00
|
|
|
// Emit an event with a summary of operation counts.
|
2018-01-25 03:22:41 +01:00
|
|
|
changes := ResourceChanges(actions.Ops)
|
2018-03-05 20:39:50 +01:00
|
|
|
result.Options.Events.previewSummaryEvent(changes)
|
2018-01-25 03:22:41 +01:00
|
|
|
return changes, nil
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2018-02-03 01:02:50 +01:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2018-02-04 10:29:30 +01:00
|
|
|
// getIndent computes a step's parent indentation.
|
|
|
|
func getIndent(step deploy.Step, seen map[resource.URN]deploy.Step) int {
|
2018-02-04 08:37:22 +01:00
|
|
|
indent := 0
|
2017-11-17 03:21:41 +01:00
|
|
|
for p := step.Res().Parent; p != ""; {
|
|
|
|
par := seen[p]
|
|
|
|
if par == nil {
|
|
|
|
// This can happen during deletes, since we delete children before parents.
|
|
|
|
// TODO[pulumi/pulumi#340]: we need to figure out how best to display this sequence; at the very
|
|
|
|
// least, it would be ideal to preserve the indentation.
|
|
|
|
break
|
|
|
|
}
|
|
|
|
indent++
|
|
|
|
p = par.Res().Parent
|
|
|
|
}
|
2017-11-26 00:54:01 +01:00
|
|
|
return indent
|
|
|
|
}
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
func printStepHeader(b *bytes.Buffer, step deploy.Step) {
|
Implement resource protection (#751)
This change implements resource protection, as per pulumi/pulumi#689.
The overall idea is that a resource can be marked as "protect: true",
which will prevent deletion of that resource for any reason whatsoever
(straight deletion, replacement, etc). This is expressed in the
program. To "unprotect" a resource, one must perform an update setting
"protect: false", and then afterwards, they can delete the resource.
For example:
let res = new MyResource("precious", { .. }, { protect: true });
Afterwards, the resource will display in the CLI with a lock icon, and
any attempts to remove it will fail in the usual ways (in planning or,
worst case, during an actual update).
This was done by adding a new ResourceOptions bag parameter to the
base Resource types. This is unfortunately a breaking change, but now
is the right time to take this one. We had been adding new settings
one by one -- like parent and dependsOn -- and this new approach will
set us up to add any number of additional settings down the road,
without needing to worry about breaking anything ever again.
This is related to protected stacks, as described in
pulumi/pulumi-service#399. Most likely this will serve as a foundational
building block that enables the coarser grained policy management.
2017-12-20 23:31:07 +01:00
|
|
|
var extra string
|
|
|
|
old := step.Old()
|
|
|
|
new := step.New()
|
|
|
|
if new != nil && !new.Protect && old != nil && old.Protect {
|
|
|
|
// show an unlocked symbol, since we are unprotecting a resource.
|
|
|
|
extra = " 🔓"
|
|
|
|
} else if (new != nil && new.Protect) || (old != nil && old.Protect) {
|
|
|
|
// show a locked symbol, since we are either newly protecting this resource, or retaining protection.
|
|
|
|
extra = " 🔒"
|
|
|
|
}
|
|
|
|
b.WriteString(fmt.Sprintf("%s: (%s)%s\n", string(step.Type()), step.Op(), extra))
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2017-11-26 00:54:01 +01:00
|
|
|
func getIndentationString(indent int, op deploy.StepOp, prefix bool) string {
|
|
|
|
var result string
|
2017-11-20 20:39:49 +01:00
|
|
|
for i := 0; i < indent; i++ {
|
|
|
|
result += " "
|
|
|
|
}
|
|
|
|
|
2017-11-29 19:06:51 +01:00
|
|
|
if result == "" {
|
|
|
|
contract.Assertf(!prefix, "Expected indention for a prefixed line")
|
2017-11-20 20:39:49 +01:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2017-11-29 19:06:51 +01:00
|
|
|
var rp string
|
|
|
|
if prefix {
|
|
|
|
rp = op.RawPrefix()
|
|
|
|
} else {
|
|
|
|
rp = " "
|
|
|
|
}
|
|
|
|
contract.Assert(len(rp) == 2)
|
|
|
|
contract.Assert(len(result) >= 2)
|
|
|
|
return result[:len(result)-2] + rp
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
2017-11-29 19:06:51 +01:00
|
|
|
func writeWithIndent(b *bytes.Buffer, indent int, op deploy.StepOp, prefix bool, format string, a ...interface{}) {
|
|
|
|
b.WriteString(op.Color())
|
|
|
|
b.WriteString(getIndentationString(indent, op, prefix))
|
2017-11-20 20:39:49 +01:00
|
|
|
b.WriteString(fmt.Sprintf(format, a...))
|
|
|
|
b.WriteString(colors.Reset)
|
|
|
|
}
|
|
|
|
|
2017-11-26 00:54:01 +01:00
|
|
|
func writeWithIndentNoPrefix(b *bytes.Buffer, indent int, op deploy.StepOp, format string, a ...interface{}) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndent(b, indent, op, false, format, a...)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func write(b *bytes.Buffer, op deploy.StepOp, format string, a ...interface{}) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndentNoPrefix(b, 0, op, format, a...)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func writeVerbatim(b *bytes.Buffer, op deploy.StepOp, value string) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndentNoPrefix(b, 0, op, "%s", value)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
2018-02-04 10:18:06 +01:00
|
|
|
func getResourcePropertiesSummary(step deploy.Step, indent int) string {
|
|
|
|
var b bytes.Buffer
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2018-02-04 10:18:06 +01:00
|
|
|
op := step.Op()
|
|
|
|
urn := step.URN()
|
|
|
|
old := step.Old()
|
|
|
|
|
|
|
|
// Print the indentation.
|
|
|
|
b.WriteString(getIndentationString(indent, op, false))
|
|
|
|
|
|
|
|
// First, print out the operation's prefix.
|
|
|
|
b.WriteString(op.Prefix())
|
|
|
|
|
|
|
|
// Next, print the resource type (since it is easy on the eyes and can be quickly identified).
|
|
|
|
printStepHeader(&b, step)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
// For these simple properties, print them as 'same' if they're just an update or replace.
|
2017-11-26 00:54:01 +01:00
|
|
|
simplePropOp := considerSameIfNotCreateOrDelete(op)
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2018-02-04 10:18:06 +01:00
|
|
|
// Print out the URN and, if present, the ID, as "pseudo-properties" and indent them.
|
2017-11-26 00:54:01 +01:00
|
|
|
var id resource.ID
|
2017-08-23 01:56:15 +02:00
|
|
|
if old != nil {
|
|
|
|
id = old.ID
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-11-26 00:54:01 +01:00
|
|
|
// Always print the ID and URN.
|
2017-08-23 01:56:15 +02:00
|
|
|
if id != "" {
|
2018-02-04 10:18:06 +01:00
|
|
|
writeWithIndentNoPrefix(&b, indent+1, simplePropOp, "[id=%s]\n", string(id))
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
if urn != "" {
|
2018-02-04 10:18:06 +01:00
|
|
|
writeWithIndentNoPrefix(&b, indent+1, simplePropOp, "[urn=%s]\n", urn)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2018-02-04 10:18:06 +01:00
|
|
|
return b.String()
|
|
|
|
}
|
|
|
|
|
|
|
|
func getResourcePropertiesDetails(step deploy.Step, indent int, planning bool, debug bool) string {
|
|
|
|
var b bytes.Buffer
|
|
|
|
|
|
|
|
// indent everything an additional level, like other properties.
|
|
|
|
indent++
|
|
|
|
|
|
|
|
var replaces []resource.PropertyKey
|
|
|
|
if step.Op() == deploy.OpCreateReplacement {
|
|
|
|
replaces = step.(*deploy.CreateStep).Keys()
|
|
|
|
} else if step.Op() == deploy.OpReplace {
|
|
|
|
replaces = step.(*deploy.ReplaceStep).Keys()
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2018-02-04 10:18:06 +01:00
|
|
|
|
|
|
|
old := step.Old()
|
|
|
|
new := step.New()
|
|
|
|
|
|
|
|
if old == nil && new != nil {
|
|
|
|
printObject(&b, new.Inputs, planning, indent, step.Op(), false, debug)
|
|
|
|
} else if new == nil && old != nil {
|
|
|
|
printObject(&b, old.Inputs, planning, indent, step.Op(), false, debug)
|
|
|
|
} else {
|
|
|
|
printOldNewDiffs(&b, old.Inputs, new.Inputs, replaces, planning, indent, step.Op(), debug)
|
|
|
|
}
|
|
|
|
|
|
|
|
return b.String()
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func maxKey(keys []resource.PropertyKey) int {
|
|
|
|
maxkey := 0
|
|
|
|
for _, k := range keys {
|
|
|
|
if len(k) > maxkey {
|
|
|
|
maxkey = len(k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return maxkey
|
|
|
|
}
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
func printObject(
|
|
|
|
b *bytes.Buffer, props resource.PropertyMap, planning bool,
|
2018-03-08 06:10:12 +01:00
|
|
|
indent int, op deploy.StepOp, prefix bool, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// Compute the maximum with of property keys so we can justify everything.
|
|
|
|
keys := props.StableKeys()
|
|
|
|
maxkey := maxKey(keys)
|
|
|
|
|
|
|
|
// Now print out the values intelligently based on the type.
|
|
|
|
for _, k := range keys {
|
|
|
|
if v := props[k]; shouldPrintPropertyValue(v, planning) {
|
2017-11-26 00:54:01 +01:00
|
|
|
printPropertyTitle(b, string(k), maxkey, indent, op, prefix)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, v, planning, indent, op, prefix, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// printResourceOutputProperties prints only those properties that either differ from the input properties or, if
|
|
|
|
// there is an old snapshot of the resource, differ from the prior old snapshot's output properties.
|
2018-02-04 10:18:06 +01:00
|
|
|
func getResourceOutputsPropertiesString(step deploy.Step, indent int, planning bool, debug bool) string {
|
|
|
|
var b bytes.Buffer
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// Only certain kinds of steps have output properties associated with them.
|
2017-12-07 22:33:16 +01:00
|
|
|
new := step.New()
|
|
|
|
if new == nil || new.Outputs == nil {
|
2018-02-04 10:18:06 +01:00
|
|
|
return ""
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-12-07 22:33:16 +01:00
|
|
|
op := considerSameIfNotCreateOrDelete(step.Op())
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
// First fetch all the relevant property maps that we may consult.
|
2017-12-07 22:33:16 +01:00
|
|
|
ins := new.Inputs
|
|
|
|
outs := new.Outputs
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
// Now sort the keys and enumerate each output property in a deterministic order.
|
|
|
|
firstout := true
|
2017-12-07 22:33:16 +01:00
|
|
|
keys := outs.StableKeys()
|
2017-08-23 01:56:15 +02:00
|
|
|
maxkey := maxKey(keys)
|
|
|
|
for _, k := range keys {
|
2017-12-07 22:33:16 +01:00
|
|
|
out := outs[k]
|
|
|
|
// Print this property if it is printable and either ins doesn't have it or it's different.
|
|
|
|
if shouldPrintPropertyValue(out, true) {
|
2017-08-23 01:56:15 +02:00
|
|
|
var print bool
|
2017-12-07 22:33:16 +01:00
|
|
|
if in, has := ins[k]; has {
|
|
|
|
print = (out.Diff(in) != nil)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-12-07 22:33:16 +01:00
|
|
|
print = true
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if print {
|
|
|
|
if firstout {
|
2018-02-04 10:18:06 +01:00
|
|
|
writeWithIndentNoPrefix(&b, indent, op, "---outputs:---\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
firstout = false
|
|
|
|
}
|
2018-02-04 10:18:06 +01:00
|
|
|
printPropertyTitle(&b, string(k), maxkey, indent, op, false)
|
|
|
|
printPropertyValue(&b, out, planning, indent, op, false, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-04 10:18:06 +01:00
|
|
|
|
|
|
|
return b.String()
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func considerSameIfNotCreateOrDelete(op deploy.StepOp) deploy.StepOp {
|
|
|
|
if op == deploy.OpCreate || op == deploy.OpDelete || op == deploy.OpDeleteReplaced {
|
|
|
|
return op
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
return deploy.OpSame
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func shouldPrintPropertyValue(v resource.PropertyValue, outs bool) bool {
|
|
|
|
if v.IsNull() {
|
2017-11-17 03:21:41 +01:00
|
|
|
return false // don't print nulls (they just clutter up the output).
|
|
|
|
}
|
|
|
|
if v.IsString() && v.StringValue() == "" {
|
|
|
|
return false // don't print empty strings either.
|
|
|
|
}
|
|
|
|
if v.IsArray() && len(v.ArrayValue()) == 0 {
|
|
|
|
return false // skip empty arrays, since they are often uninteresting default values.
|
|
|
|
}
|
|
|
|
if v.IsObject() && len(v.ObjectValue()) == 0 {
|
|
|
|
return false // skip objects with no properties, since they are also uninteresting.
|
|
|
|
}
|
|
|
|
if v.IsObject() && len(v.ObjectValue()) == 0 {
|
|
|
|
return false // skip objects with no properties, since they are also uninteresting.
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
if v.IsOutput() && !outs {
|
|
|
|
// also don't show output properties until the outs parameter tells us to.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2017-11-26 00:54:01 +01:00
|
|
|
func printPropertyTitle(b *bytes.Buffer, name string, align int, indent int, op deploy.StepOp, prefix bool) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndent(b, indent, op, prefix, "%-"+strconv.Itoa(align)+"s: ", name)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
func printPropertyValue(
|
|
|
|
b *bytes.Buffer, v resource.PropertyValue, planning bool,
|
2018-03-08 06:10:12 +01:00
|
|
|
indent int, op deploy.StepOp, prefix bool, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
if v.IsNull() {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "<null>")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if v.IsBool() {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "%t", v.BoolValue())
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if v.IsNumber() {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "%v", v.NumberValue())
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if v.IsString() {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "%q", v.StringValue())
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if v.IsArray() {
|
|
|
|
arr := v.ArrayValue()
|
|
|
|
if len(arr) == 0 {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "[]")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "[\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
for i, elem := range arr {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndent(b, indent, op, prefix, " [%d]: ", i)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, elem, planning, indent+1, op, prefix, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "]")
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
} else if v.IsAsset() {
|
|
|
|
a := v.AssetValue()
|
|
|
|
if text, has := a.GetText(); has {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(text:%s) {\n", shortHash(a.Hash))
|
|
|
|
|
2018-03-08 06:10:12 +01:00
|
|
|
massaged := massageText(text, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// pretty print the text, line by line, with proper breaks.
|
2017-11-20 20:39:49 +01:00
|
|
|
lines := strings.Split(massaged, "\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
for _, line := range lines {
|
2017-11-26 00:54:01 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, " %s\n", line)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "}")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if path, has := a.GetPath(); has {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(file:%s) { %s }", shortHash(a.Hash), path)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
|
|
|
contract.Assert(a.IsURI())
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(uri:%s) { %s }", shortHash(a.Hash), a.URI)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
} else if v.IsArchive() {
|
|
|
|
a := v.ArchiveValue()
|
|
|
|
if assets, has := a.GetAssets(); has {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(assets:%s) {\n", shortHash(a.Hash))
|
2017-08-23 01:56:15 +02:00
|
|
|
var names []string
|
|
|
|
for name := range assets {
|
|
|
|
names = append(names, name)
|
|
|
|
}
|
|
|
|
sort.Strings(names)
|
|
|
|
for _, name := range names {
|
2018-03-08 06:10:12 +01:00
|
|
|
printAssetOrArchive(b, assets[name], name, planning, indent, op, prefix, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "}")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if path, has := a.GetPath(); has {
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(file:%s) { %s }", shortHash(a.Hash), path)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
|
|
|
contract.Assert(a.IsURI())
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(uri:%s) { %v }", shortHash(a.Hash), a.URI)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
} else if v.IsComputed() || v.IsOutput() {
|
2017-12-12 23:32:27 +01:00
|
|
|
// We render computed and output values differently depending on whether or not we are planning or deploying:
|
|
|
|
// in the former case, we display `computed<type>` or `output<type>`; in the former we display `undefined`.
|
|
|
|
// This is because we currently cannot distinguish between user-supplied undefined values and input properties
|
|
|
|
// that are undefined because they were sourced from undefined values in other resources' output properties.
|
|
|
|
// Once we have richer information about the dataflow between resources, we should be able to do a better job
|
|
|
|
// here (pulumi/pulumi#234).
|
2017-12-12 23:19:29 +01:00
|
|
|
if planning {
|
|
|
|
writeVerbatim(b, op, v.TypeString())
|
|
|
|
} else {
|
|
|
|
write(b, op, "undefined")
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
|
|
|
contract.Assert(v.IsObject())
|
|
|
|
obj := v.ObjectValue()
|
|
|
|
if len(obj) == 0 {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "{}")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "{\n")
|
2018-03-08 06:10:12 +01:00
|
|
|
printObject(b, obj, planning, indent+1, op, prefix, debug)
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "}")
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
func printAssetOrArchive(
|
|
|
|
b *bytes.Buffer, v interface{}, name string, planning bool,
|
2018-03-08 06:10:12 +01:00
|
|
|
indent int, op deploy.StepOp, prefix bool, debug bool) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndent(b, indent, op, prefix, " \"%v\": ", name)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, assetOrArchiveToPropertyValue(v), planning, indent+1, op, prefix, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func assetOrArchiveToPropertyValue(v interface{}) resource.PropertyValue {
|
|
|
|
switch t := v.(type) {
|
|
|
|
case *resource.Asset:
|
|
|
|
return resource.NewAssetProperty(t)
|
|
|
|
case *resource.Archive:
|
|
|
|
return resource.NewArchiveProperty(t)
|
|
|
|
default:
|
|
|
|
contract.Failf("Unexpected archive element '%v'", reflect.TypeOf(t))
|
|
|
|
return resource.PropertyValue{V: nil}
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
|
2017-10-22 22:39:21 +02:00
|
|
|
func shortHash(hash string) string {
|
|
|
|
if len(hash) > 7 {
|
|
|
|
return hash[:7]
|
|
|
|
}
|
|
|
|
return hash
|
|
|
|
}
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
func printOldNewDiffs(
|
|
|
|
b *bytes.Buffer, olds resource.PropertyMap, news resource.PropertyMap,
|
2018-01-31 20:33:34 +01:00
|
|
|
replaces []resource.PropertyKey, planning bool, indent int, op deploy.StepOp, debug bool) {
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
// Get the full diff structure between the two, and print it (recursively).
|
|
|
|
if diff := olds.Diff(news); diff != nil {
|
2018-01-31 20:33:34 +01:00
|
|
|
printObjectDiff(b, *diff, replaces, false, planning, indent, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2018-03-08 06:10:12 +01:00
|
|
|
printObject(b, news, planning, indent, op, true, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-31 20:33:34 +01:00
|
|
|
func printObjectDiff(b *bytes.Buffer, diff resource.ObjectDiff,
|
|
|
|
replaces []resource.PropertyKey, causedReplace bool, planning bool, indent int, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
contract.Assert(indent > 0)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
// Compute the maximum with of property keys so we can justify everything.
|
|
|
|
keys := diff.Keys()
|
|
|
|
maxkey := maxKey(keys)
|
|
|
|
|
|
|
|
// If a list of what causes a resource to get replaced exist, create a handy map.
|
|
|
|
var replaceMap map[resource.PropertyKey]bool
|
|
|
|
if len(replaces) > 0 {
|
|
|
|
replaceMap = make(map[resource.PropertyKey]bool)
|
|
|
|
for _, k := range replaces {
|
|
|
|
replaceMap[k] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// To print an object diff, enumerate the keys in stable order, and print each property independently.
|
|
|
|
for _, k := range keys {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc := func(top deploy.StepOp, prefix bool) {
|
|
|
|
printPropertyTitle(b, string(k), maxkey, indent, top, prefix)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
if add, isadd := diff.Adds[k]; isadd {
|
|
|
|
if shouldPrintPropertyValue(add, planning) {
|
2018-03-08 06:10:12 +01:00
|
|
|
printAdd(b, add, titleFunc, true, planning, indent, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
} else if delete, isdelete := diff.Deletes[k]; isdelete {
|
|
|
|
if shouldPrintPropertyValue(delete, planning) {
|
2018-03-08 06:10:12 +01:00
|
|
|
printDelete(b, delete, titleFunc, true, planning, indent, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
} else if update, isupdate := diff.Updates[k]; isupdate {
|
|
|
|
if !causedReplace && replaceMap != nil {
|
|
|
|
causedReplace = replaceMap[k]
|
|
|
|
}
|
2018-01-31 20:33:34 +01:00
|
|
|
|
|
|
|
printPropertyValueDiff(b, titleFunc, update, causedReplace, planning, indent, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if same := diff.Sames[k]; shouldPrintPropertyValue(same, planning) {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(deploy.OpSame, false)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, diff.Sames[k], planning, indent, deploy.OpSame, false, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-26 00:54:01 +01:00
|
|
|
func printPropertyValueDiff(b *bytes.Buffer, titleFunc func(deploy.StepOp, bool),
|
2018-01-31 20:33:34 +01:00
|
|
|
diff resource.ValueDiff, causedReplace bool, planning bool, indent int, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
op := deploy.OpUpdate
|
|
|
|
contract.Assert(indent > 0)
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
if diff.Array != nil {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "[\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
|
|
|
|
a := diff.Array
|
|
|
|
for i := 0; i < a.Len(); i++ {
|
2017-11-26 00:54:01 +01:00
|
|
|
elemTitleFunc := func(eop deploy.StepOp, eprefix bool) {
|
2017-11-29 19:06:51 +01:00
|
|
|
writeWithIndent(b, indent+1, eop, eprefix, "[%d]: ", i)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
if add, isadd := a.Adds[i]; isadd {
|
2018-03-08 06:10:12 +01:00
|
|
|
printAdd(b, add, elemTitleFunc, true, planning, indent+2, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if delete, isdelete := a.Deletes[i]; isdelete {
|
2018-03-08 06:10:12 +01:00
|
|
|
printDelete(b, delete, elemTitleFunc, true, planning, indent+2, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if update, isupdate := a.Updates[i]; isupdate {
|
2018-01-31 20:33:34 +01:00
|
|
|
printPropertyValueDiff(b, elemTitleFunc, update, causedReplace, planning, indent+2, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-11-26 00:54:01 +01:00
|
|
|
elemTitleFunc(deploy.OpSame, false)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, a.Sames[i], planning, indent+2, deploy.OpSame, false, debug)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
2017-11-29 20:27:32 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "]\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else if diff.Object != nil {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
writeVerbatim(b, op, "{\n")
|
2018-01-31 20:33:34 +01:00
|
|
|
printObjectDiff(b, *diff.Object, nil, causedReplace, planning, indent+1, debug)
|
2017-11-29 20:27:32 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "}\n")
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-11-20 20:39:49 +01:00
|
|
|
shouldPrintOld := shouldPrintPropertyValue(diff.Old, false)
|
|
|
|
shouldPrintNew := shouldPrintPropertyValue(diff.New, false)
|
|
|
|
|
|
|
|
if diff.Old.IsArchive() &&
|
|
|
|
diff.New.IsArchive() &&
|
|
|
|
!causedReplace &&
|
|
|
|
shouldPrintOld &&
|
|
|
|
shouldPrintNew {
|
2018-03-08 06:10:12 +01:00
|
|
|
printArchiveDiff(b, titleFunc, diff.Old.ArchiveValue(), diff.New.ArchiveValue(), planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-23 01:56:15 +02:00
|
|
|
// If we ended up here, the two values either differ by type, or they have different primitive values. We will
|
|
|
|
// simply emit a deletion line followed by an addition line.
|
2017-11-20 20:39:49 +01:00
|
|
|
if shouldPrintOld {
|
2018-03-08 06:10:12 +01:00
|
|
|
printDelete(b, diff.Old, titleFunc, causedReplace, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
if shouldPrintNew {
|
2018-03-08 06:10:12 +01:00
|
|
|
printAdd(b, diff.New, titleFunc, causedReplace, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func printDelete(
|
2017-11-26 00:54:01 +01:00
|
|
|
b *bytes.Buffer, v resource.PropertyValue, title func(deploy.StepOp, bool),
|
2018-03-08 06:10:12 +01:00
|
|
|
causedReplace bool, planning bool, indent int, debug bool) {
|
2017-11-26 00:54:01 +01:00
|
|
|
op := deploy.OpDelete
|
|
|
|
title(op, true)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, v, planning, indent, op, true, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func printAdd(
|
2017-11-26 00:54:01 +01:00
|
|
|
b *bytes.Buffer, v resource.PropertyValue, title func(deploy.StepOp, bool),
|
2018-03-08 06:10:12 +01:00
|
|
|
causedReplace bool, planning bool, indent int, debug bool) {
|
2017-11-26 00:54:01 +01:00
|
|
|
op := deploy.OpCreate
|
|
|
|
title(op, true)
|
2018-03-08 06:10:12 +01:00
|
|
|
printPropertyValue(b, v, planning, indent, op, true, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func printArchiveDiff(
|
2017-11-26 00:54:01 +01:00
|
|
|
b *bytes.Buffer, titleFunc func(deploy.StepOp, bool),
|
2017-11-20 20:39:49 +01:00
|
|
|
oldArchive *resource.Archive, newArchive *resource.Archive,
|
2018-03-08 06:10:12 +01:00
|
|
|
planning bool, indent int, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
// TODO: this could be called recursively from itself. In the recursive case, we might have an
|
|
|
|
// archive that actually hasn't changed. Check for that, and terminate the diff printing.
|
|
|
|
|
|
|
|
op := deploy.OpUpdate
|
|
|
|
|
|
|
|
hashChange := getTextChangeString(shortHash(oldArchive.Hash), shortHash(newArchive.Hash))
|
|
|
|
|
|
|
|
if oldPath, has := oldArchive.GetPath(); has {
|
|
|
|
if newPath, has := newArchive.GetPath(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(file:%s) { %s }\n", hashChange, getTextChangeString(oldPath, newPath))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if oldURI, has := oldArchive.GetURI(); has {
|
|
|
|
if newURI, has := newArchive.GetURI(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(uri:%s) { %s }\n", hashChange, getTextChangeString(oldURI, newURI))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
contract.Assert(oldArchive.IsAssets())
|
|
|
|
oldAssets, _ := oldArchive.GetAssets()
|
|
|
|
|
|
|
|
if newAssets, has := newArchive.GetAssets(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "archive(assets:%s) {\n", hashChange)
|
2018-03-08 06:10:12 +01:00
|
|
|
printAssetsDiff(b, oldAssets, newAssets, planning, indent+1, debug)
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, deploy.OpUpdate, "}\n")
|
2017-11-20 20:39:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type of archive changed, print this out as an remove and an add.
|
|
|
|
printDelete(
|
|
|
|
b, assetOrArchiveToPropertyValue(oldArchive),
|
2018-03-08 06:10:12 +01:00
|
|
|
titleFunc, false /*causedReplace*/, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
printAdd(
|
|
|
|
b, assetOrArchiveToPropertyValue(newArchive),
|
2018-03-08 06:10:12 +01:00
|
|
|
titleFunc, false /*causedReplace*/, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func printAssetsDiff(
|
|
|
|
b *bytes.Buffer,
|
|
|
|
oldAssets map[string]interface{}, newAssets map[string]interface{},
|
2018-03-08 06:10:12 +01:00
|
|
|
planning bool, indent int, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
// Diffing assets proceeds by getting the sorted list of asset names from both the old and
|
|
|
|
// new assets, and then stepwise processing each. For any asset in old that isn't in new,
|
|
|
|
// we print this out as a delete. For any asset in new that isn't in old, we print this out
|
|
|
|
// as an add. For any asset in both we print out of it is unchanged or not. If so, we
|
|
|
|
// recurse on that data to print out how it changed.
|
|
|
|
|
|
|
|
var oldNames []string
|
|
|
|
var newNames []string
|
|
|
|
|
|
|
|
for name := range oldAssets {
|
|
|
|
oldNames = append(oldNames, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
for name := range newAssets {
|
|
|
|
newNames = append(newNames, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Strings(oldNames)
|
|
|
|
sort.Strings(newNames)
|
|
|
|
|
|
|
|
i := 0
|
|
|
|
j := 0
|
|
|
|
|
|
|
|
var keys []resource.PropertyKey
|
|
|
|
for _, name := range oldNames {
|
|
|
|
keys = append(keys, "\""+resource.PropertyKey(name)+"\"")
|
|
|
|
}
|
|
|
|
for _, name := range newNames {
|
|
|
|
keys = append(keys, "\""+resource.PropertyKey(name)+"\"")
|
|
|
|
}
|
|
|
|
|
|
|
|
maxkey := maxKey(keys)
|
|
|
|
|
|
|
|
for i < len(oldNames) || j < len(newNames) {
|
|
|
|
deleteOld := false
|
|
|
|
addNew := false
|
|
|
|
if i < len(oldNames) && j < len(newNames) {
|
|
|
|
oldName := oldNames[i]
|
|
|
|
newName := newNames[j]
|
|
|
|
|
|
|
|
if oldName == newName {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc := func(top deploy.StepOp, tprefix bool) {
|
|
|
|
printPropertyTitle(b, "\""+oldName+"\"", maxkey, indent, top, tprefix)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
oldAsset := oldAssets[oldName]
|
|
|
|
newAsset := newAssets[newName]
|
|
|
|
|
|
|
|
switch t := oldAsset.(type) {
|
|
|
|
case *resource.Archive:
|
2018-03-08 06:10:12 +01:00
|
|
|
printArchiveDiff(b, titleFunc, t, newAsset.(*resource.Archive), planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
case *resource.Asset:
|
2018-03-08 06:10:12 +01:00
|
|
|
printAssetDiff(b, titleFunc, t, newAsset.(*resource.Asset), planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
i++
|
|
|
|
j++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if oldName < newName {
|
|
|
|
deleteOld = true
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-11-20 20:39:49 +01:00
|
|
|
addNew = true
|
|
|
|
}
|
|
|
|
} else if i < len(oldNames) {
|
|
|
|
deleteOld = true
|
|
|
|
} else {
|
|
|
|
addNew = true
|
|
|
|
}
|
|
|
|
|
|
|
|
newIndent := indent + 1
|
|
|
|
if deleteOld {
|
|
|
|
oldName := oldNames[i]
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc := func(top deploy.StepOp, tprefix bool) {
|
|
|
|
printPropertyTitle(b, "\""+oldName+"\"", maxkey, indent, top, tprefix)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
2018-03-08 06:10:12 +01:00
|
|
|
printDelete(b, assetOrArchiveToPropertyValue(oldAssets[oldName]), titleFunc, false, planning, newIndent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
i++
|
|
|
|
continue
|
|
|
|
} else {
|
|
|
|
contract.Assert(addNew)
|
|
|
|
newName := newNames[j]
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc := func(top deploy.StepOp, tprefix bool) {
|
|
|
|
printPropertyTitle(b, "\""+newName+"\"", maxkey, indent, top, tprefix)
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2018-03-08 06:10:12 +01:00
|
|
|
printAdd(b, assetOrArchiveToPropertyValue(newAssets[newName]), titleFunc, false, planning, newIndent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
j++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeAssetHeader(asset *resource.Asset) string {
|
|
|
|
var assetType string
|
|
|
|
var contents string
|
|
|
|
|
|
|
|
if path, has := asset.GetPath(); has {
|
|
|
|
assetType = "file"
|
|
|
|
contents = path
|
|
|
|
} else if uri, has := asset.GetURI(); has {
|
|
|
|
assetType = "uri"
|
|
|
|
contents = uri
|
|
|
|
} else {
|
|
|
|
assetType = "text"
|
|
|
|
contents = "..."
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("asset(%s:%s) { %s }\n", assetType, shortHash(asset.Hash), contents)
|
|
|
|
}
|
|
|
|
|
|
|
|
func printAssetDiff(
|
2017-11-26 00:54:01 +01:00
|
|
|
b *bytes.Buffer, titleFunc func(deploy.StepOp, bool),
|
2017-11-20 20:39:49 +01:00
|
|
|
oldAsset *resource.Asset, newAsset *resource.Asset,
|
2018-03-08 06:10:12 +01:00
|
|
|
planning bool, indent int, debug bool) {
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
op := deploy.OpUpdate
|
|
|
|
|
|
|
|
// If the assets aren't changed, just print out: = assetName: type(hash)
|
|
|
|
if oldAsset.Hash == newAsset.Hash {
|
|
|
|
op = deploy.OpSame
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(op, false)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, makeAssetHeader(oldAsset))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// if the asset changed, print out: ~ assetName: type(hash->hash) details...
|
|
|
|
|
|
|
|
hashChange := getTextChangeString(shortHash(oldAsset.Hash), shortHash(newAsset.Hash))
|
|
|
|
|
|
|
|
if oldText, has := oldAsset.GetText(); has {
|
|
|
|
if newText, has := newAsset.GetText(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(deploy.OpUpdate, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(text:%s) {\n", hashChange)
|
|
|
|
|
2018-03-08 06:10:12 +01:00
|
|
|
massagedOldText := massageText(oldText, debug)
|
|
|
|
massagedNewText := massageText(newText, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
differ := diffmatchpatch.New()
|
|
|
|
differ.DiffTimeout = 0
|
|
|
|
|
|
|
|
hashed1, hashed2, lineArray := differ.DiffLinesToChars(massagedOldText, massagedNewText)
|
|
|
|
diffs1 := differ.DiffMain(hashed1, hashed2, false)
|
|
|
|
diffs2 := differ.DiffCharsToLines(diffs1, lineArray)
|
|
|
|
|
|
|
|
b.WriteString(diffToPrettyString(diffs2, indent+1))
|
|
|
|
|
2017-12-18 23:51:23 +01:00
|
|
|
writeWithIndentNoPrefix(b, indent, op, "}\n")
|
2017-11-20 20:39:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
} else if oldPath, has := oldAsset.GetPath(); has {
|
|
|
|
if newPath, has := newAsset.GetPath(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(deploy.OpUpdate, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(file:%s) { %s }\n", hashChange, getTextChangeString(oldPath, newPath))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
contract.Assert(oldAsset.IsURI())
|
|
|
|
|
|
|
|
oldURI, _ := oldAsset.GetURI()
|
|
|
|
if newURI, has := newAsset.GetURI(); has {
|
2017-11-26 00:54:01 +01:00
|
|
|
titleFunc(deploy.OpUpdate, true)
|
2017-11-20 20:39:49 +01:00
|
|
|
write(b, op, "asset(uri:%s) { %s }\n", hashChange, getTextChangeString(oldURI, newURI))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type of asset changed, print this out as an remove and an add.
|
|
|
|
printDelete(
|
|
|
|
b, assetOrArchiveToPropertyValue(oldAsset),
|
2018-03-08 06:10:12 +01:00
|
|
|
titleFunc, false /*causedReplace*/, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
printAdd(
|
|
|
|
b, assetOrArchiveToPropertyValue(newAsset),
|
2018-03-08 06:10:12 +01:00
|
|
|
titleFunc, false /*causedReplace*/, planning, indent, debug)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func getTextChangeString(old string, new string) string {
|
|
|
|
if old == new {
|
|
|
|
return old
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s->%s", old, new)
|
|
|
|
}
|
|
|
|
|
2017-12-14 23:40:04 +01:00
|
|
|
var (
|
2018-03-13 00:27:00 +01:00
|
|
|
functionRegexp = regexp.MustCompile(`function __.*`)
|
2017-12-14 23:55:27 +01:00
|
|
|
withRegexp = regexp.MustCompile(` with\({ .* }\) {`)
|
2017-12-14 23:40:04 +01:00
|
|
|
environmentRegexp = regexp.MustCompile(` }\).apply\(.*\).apply\(this, arguments\);`)
|
2017-12-15 00:41:53 +01:00
|
|
|
preambleRegexp = regexp.MustCompile(
|
2018-03-13 00:27:00 +01:00
|
|
|
`function __.*\(\) {\n return \(function\(\) {\n with \(__closure\) {\n\nreturn `)
|
2017-12-15 00:41:53 +01:00
|
|
|
postambleRegexp = regexp.MustCompile(
|
2018-03-01 09:32:01 +01:00
|
|
|
`;\n\n }\n }\).apply\(__environment\).apply\(this, arguments\);\n}`)
|
2017-12-14 23:40:04 +01:00
|
|
|
)
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
// massageText takes the text for a function and cleans it up a bit to make the user visible diffs
|
|
|
|
// less noisy. Specifically:
|
|
|
|
// 1. it tries to condense things by changling multiple blank lines into a single blank line.
|
|
|
|
// 2. it normalizs the sha hashes we emit so that changes to them don't appear in the diff.
|
|
|
|
// 3. it elides the with-capture headers, as changes there are not generally meaningful.
|
|
|
|
//
|
|
|
|
// TODO(https://github.com/pulumi/pulumi/issues/592) this is baking in a lot of knowledge about
|
|
|
|
// pulumi serialized functions. We should try to move to an alternative mode that isn't so brittle.
|
|
|
|
// Options include:
|
|
|
|
// 1. Have a documented delimeter format that plan.go will look for. Have the function serializer
|
|
|
|
// emit those delimeters around code that should be ignored.
|
|
|
|
// 2. Have our resource generation code supply not just the resource, but the "user presentable"
|
|
|
|
// resource that cuts out a lot of cruft. We could then just diff that content here.
|
2018-03-08 06:10:12 +01:00
|
|
|
func massageText(text string, debug bool) string {
|
|
|
|
if debug {
|
|
|
|
return text
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
// Only do this for strings that match our serialized function pattern.
|
2018-03-01 09:32:01 +01:00
|
|
|
if !functionRegexp.MatchString(text) ||
|
2017-12-14 23:40:04 +01:00
|
|
|
!withRegexp.MatchString(text) ||
|
|
|
|
!environmentRegexp.MatchString(text) {
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
2017-12-14 23:55:27 +01:00
|
|
|
replaceNewlines := func() {
|
|
|
|
for {
|
|
|
|
newText := strings.Replace(text, "\n\n\n", "\n\n", -1)
|
|
|
|
if len(newText) == len(text) {
|
|
|
|
break
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-12-14 23:55:27 +01:00
|
|
|
text = newText
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
2017-12-14 23:55:27 +01:00
|
|
|
replaceNewlines()
|
|
|
|
|
2018-03-01 09:32:01 +01:00
|
|
|
firstFunc := functionRegexp.FindStringIndex(text)
|
|
|
|
text = text[firstFunc[0]:]
|
|
|
|
|
2017-12-14 23:40:04 +01:00
|
|
|
text = withRegexp.ReplaceAllString(text, " with (__closure) {")
|
|
|
|
text = environmentRegexp.ReplaceAllString(text, " }).apply(__environment).apply(this, arguments);")
|
2017-11-20 20:39:49 +01:00
|
|
|
|
2017-12-14 23:55:27 +01:00
|
|
|
text = preambleRegexp.ReplaceAllString(text, "")
|
|
|
|
text = postambleRegexp.ReplaceAllString(text, "")
|
|
|
|
|
|
|
|
replaceNewlines()
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
|
|
|
// diffToPrettyString takes the full diff produed by diffmatchpatch and condenses it into something
|
|
|
|
// useful we can print to the console. Specifically, while it includes any adds/removes in
|
|
|
|
// green/red, it will also show portions of the unchanged text to help give surrounding context to
|
|
|
|
// those add/removes. Because the unchanged portions may be very large, it only included around 3
|
|
|
|
// lines before/after the change.
|
|
|
|
func diffToPrettyString(diffs []diffmatchpatch.Diff, indent int) string {
|
|
|
|
var buff bytes.Buffer
|
|
|
|
|
|
|
|
writeDiff := func(op deploy.StepOp, text string) {
|
2017-11-30 17:03:32 +01:00
|
|
|
var prefix bool
|
|
|
|
if op == deploy.OpCreate || op == deploy.OpDelete {
|
|
|
|
prefix = true
|
|
|
|
}
|
|
|
|
writeWithIndent(&buff, indent, op, prefix, "%s", text)
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for index, diff := range diffs {
|
|
|
|
text := diff.Text
|
|
|
|
lines := strings.Split(text, "\n")
|
|
|
|
printLines := func(op deploy.StepOp, startInclusive int, endExclusive int) {
|
|
|
|
for i := startInclusive; i < endExclusive; i++ {
|
2017-11-30 17:03:32 +01:00
|
|
|
if strings.TrimSpace(lines[i]) != "" {
|
|
|
|
writeDiff(op, lines[i])
|
|
|
|
buff.WriteString("\n")
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch diff.Type {
|
|
|
|
case diffmatchpatch.DiffInsert:
|
|
|
|
printLines(deploy.OpCreate, 0, len(lines))
|
|
|
|
case diffmatchpatch.DiffDelete:
|
|
|
|
printLines(deploy.OpDelete, 0, len(lines))
|
|
|
|
case diffmatchpatch.DiffEqual:
|
|
|
|
var trimmedLines []string
|
|
|
|
for _, line := range lines {
|
|
|
|
if strings.TrimSpace(line) != "" {
|
|
|
|
trimmedLines = append(trimmedLines, line)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
lines = trimmedLines
|
|
|
|
|
2017-12-14 23:55:27 +01:00
|
|
|
const contextLines = 2
|
|
|
|
|
2017-11-20 20:39:49 +01:00
|
|
|
// Show the unchanged text in white.
|
|
|
|
if index == 0 {
|
|
|
|
// First chunk of the file.
|
2017-12-14 23:55:27 +01:00
|
|
|
if len(lines) > contextLines+1 {
|
2017-11-20 20:39:49 +01:00
|
|
|
writeDiff(deploy.OpSame, "...\n")
|
2017-12-14 23:55:27 +01:00
|
|
|
printLines(deploy.OpSame, len(lines)-contextLines, len(lines))
|
2017-11-20 20:39:49 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else if index == len(diffs)-1 {
|
2017-12-14 23:55:27 +01:00
|
|
|
if len(lines) > contextLines+1 {
|
|
|
|
printLines(deploy.OpSame, 0, contextLines)
|
2017-11-20 20:39:49 +01:00
|
|
|
writeDiff(deploy.OpSame, "...\n")
|
|
|
|
continue
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
} else {
|
2017-12-14 23:55:27 +01:00
|
|
|
if len(lines) > (2*contextLines + 1) {
|
|
|
|
printLines(deploy.OpSame, 0, contextLines)
|
2017-11-20 20:39:49 +01:00
|
|
|
writeDiff(deploy.OpSame, "...\n")
|
2017-12-14 23:55:27 +01:00
|
|
|
printLines(deploy.OpSame, len(lines)-contextLines, len(lines))
|
2017-11-20 20:39:49 +01:00
|
|
|
continue
|
|
|
|
}
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
printLines(deploy.OpSame, 0, len(lines))
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|
|
|
|
}
|
2017-11-20 20:39:49 +01:00
|
|
|
|
|
|
|
return buff.String()
|
2017-08-23 01:56:15 +02:00
|
|
|
}
|