2018-09-05 14:20:25 +00:00
|
|
|
// Copyright 2016-2018, 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 backend
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
2022-11-08 21:29:59 +00:00
|
|
|
survey "github.com/AlecAivazis/survey/v2"
|
|
|
|
surveycore "github.com/AlecAivazis/survey/v2/core"
|
2018-09-05 14:20:25 +00:00
|
|
|
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/backend/display"
|
2023-09-18 11:01:28 +00:00
|
|
|
sdkDisplay "github.com/pulumi/pulumi/pkg/v3/display"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/engine"
|
2022-01-31 10:31:51 +00:00
|
|
|
"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
|
2021-03-17 13:20:05 +00:00
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
|
|
|
|
"github.com/pulumi/pulumi/sdk/v3/go/common/util/result"
|
2018-09-05 14:20:25 +00:00
|
|
|
)
|
|
|
|
|
2018-09-21 20:57:57 +00:00
|
|
|
// ApplierOptions is a bag of configuration settings for an Applier.
|
|
|
|
type ApplierOptions struct {
|
2019-03-19 18:35:17 +00:00
|
|
|
// DryRun indicates if the update should not change any resource state and instead just preview changes.
|
2018-09-21 20:57:57 +00:00
|
|
|
DryRun bool
|
2020-08-19 08:15:14 +00:00
|
|
|
// ShowLink indicates if a link to the update persisted result can be displayed.
|
2018-09-21 20:57:57 +00:00
|
|
|
ShowLink bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Applier applies the changes specified by this update operation against the target stack.
|
2018-09-05 14:20:25 +00:00
|
|
|
type Applier func(ctx context.Context, kind apitype.UpdateKind, stack Stack, op UpdateOperation,
|
2022-06-27 14:08:06 +00:00
|
|
|
opts ApplierOptions, events chan<- engine.Event) (*deploy.Plan, sdkDisplay.ResourceChanges, result.Result)
|
2018-09-05 14:20:25 +00:00
|
|
|
|
|
|
|
func ActionLabel(kind apitype.UpdateKind, dryRun bool) string {
|
|
|
|
v := updateTextMap[kind]
|
2023-02-16 20:36:43 +00:00
|
|
|
contract.Assertf(v.previewText != "", "preview text for %q cannot be empty", kind)
|
|
|
|
contract.Assertf(v.text != "", "text for %q cannot be empty", kind)
|
2018-09-05 14:20:25 +00:00
|
|
|
|
|
|
|
if dryRun {
|
|
|
|
return "Previewing " + v.previewText
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.text
|
2018-09-22 20:17:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var updateTextMap = map[apitype.UpdateKind]struct {
|
|
|
|
previewText string
|
|
|
|
text string
|
|
|
|
}{
|
2020-10-14 11:51:53 +00:00
|
|
|
apitype.PreviewUpdate: {"update", "Previewing"},
|
|
|
|
apitype.UpdateUpdate: {"update", "Updating"},
|
|
|
|
apitype.RefreshUpdate: {"refresh", "Refreshing"},
|
|
|
|
apitype.DestroyUpdate: {"destroy", "Destroying"},
|
|
|
|
apitype.StackImportUpdate: {"stack import", "Importing"},
|
|
|
|
apitype.ResourceImportUpdate: {"import", "Importing"},
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type response string
|
|
|
|
|
|
|
|
const (
|
|
|
|
yes response = "yes"
|
|
|
|
no response = "no"
|
|
|
|
details response = "details"
|
|
|
|
)
|
|
|
|
|
|
|
|
func PreviewThenPrompt(ctx context.Context, kind apitype.UpdateKind, stack Stack,
|
2023-03-03 16:36:39 +00:00
|
|
|
op UpdateOperation, apply Applier,
|
|
|
|
) (*deploy.Plan, sdkDisplay.ResourceChanges, result.Result) {
|
2018-09-05 14:20:25 +00:00
|
|
|
// create a channel to hear about the update events from the engine. this will be used so that
|
|
|
|
// we can build up the diff display in case the user asks to see the details of the diff
|
2018-09-06 00:50:48 +00:00
|
|
|
|
|
|
|
// Note that eventsChannel is not closed in a `defer`. It is generally unsafe to do so, since defers run during
|
|
|
|
// panics and we can't know whether or not we were in the middle of writing to this channel when the panic occurred.
|
|
|
|
//
|
|
|
|
// Instead of using a `defer`, we manually close `eventsChannel` on every exit of this function.
|
2018-09-05 14:20:25 +00:00
|
|
|
eventsChannel := make(chan engine.Event)
|
|
|
|
|
|
|
|
var events []engine.Event
|
|
|
|
go func() {
|
2023-10-09 18:31:17 +00:00
|
|
|
// Pull out relevant events we will want to display in the confirmation below.
|
2018-09-05 14:20:25 +00:00
|
|
|
for e := range eventsChannel {
|
2023-11-20 21:55:59 +00:00
|
|
|
// Don't include internal events in the confirmation stats.
|
|
|
|
if e.Internal() {
|
|
|
|
continue
|
|
|
|
}
|
2018-09-05 14:20:25 +00:00
|
|
|
if e.Type == engine.ResourcePreEvent ||
|
|
|
|
e.Type == engine.ResourceOutputsEvent ||
|
2023-10-09 18:31:17 +00:00
|
|
|
e.Type == engine.PolicyRemediationEvent ||
|
2018-09-05 14:20:25 +00:00
|
|
|
e.Type == engine.SummaryEvent {
|
|
|
|
events = append(events, e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Perform the update operations, passing true for dryRun, so that we get a preview.
|
2018-10-26 21:50:47 +00:00
|
|
|
// We perform the preview (DryRun), but don't display the cloud link since the
|
|
|
|
// thing the user cares about would be the link to the actual update if they
|
|
|
|
// confirm the prompt.
|
|
|
|
opts := ApplierOptions{
|
|
|
|
DryRun: true,
|
2020-08-28 20:08:10 +00:00
|
|
|
ShowLink: true,
|
2018-10-26 21:50:47 +00:00
|
|
|
}
|
2018-09-21 20:57:57 +00:00
|
|
|
|
2022-01-31 10:31:51 +00:00
|
|
|
plan, changes, res := apply(ctx, kind, stack, op, opts, eventsChannel)
|
2019-03-19 23:21:50 +00:00
|
|
|
if res != nil {
|
2018-10-26 21:50:47 +00:00
|
|
|
close(eventsChannel)
|
2022-01-31 10:31:51 +00:00
|
|
|
return plan, changes, res
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If there are no changes, or we're auto-approving or just previewing, we can skip the confirmation prompt.
|
|
|
|
if op.Opts.AutoApprove || kind == apitype.PreviewUpdate {
|
2018-09-06 00:50:48 +00:00
|
|
|
close(eventsChannel)
|
2022-11-23 08:52:43 +00:00
|
|
|
// If we're running in experimental mode then return the plan generated, else discard it. The user may
|
|
|
|
// be explicitly setting a plan but that's handled higher up the call stack.
|
|
|
|
if !op.Opts.Engine.Experimental {
|
|
|
|
plan = nil
|
|
|
|
}
|
2022-01-31 10:31:51 +00:00
|
|
|
return plan, changes, nil
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
2023-02-13 22:15:13 +00:00
|
|
|
stats := computeUpdateStats(events)
|
|
|
|
|
2022-09-07 14:41:26 +00:00
|
|
|
infoPrefix := "\b" + op.Opts.Display.Color.Colorize(colors.SpecWarning+"info: "+colors.Reset)
|
|
|
|
if kind != apitype.UpdateUpdate {
|
|
|
|
// If not an update, we can skip displaying warnings
|
2023-02-13 22:15:13 +00:00
|
|
|
} else if stats.numNonStackResources == 0 {
|
2022-09-07 14:41:26 +00:00
|
|
|
// This is an update and there are no resources being CREATED
|
2022-09-19 21:20:52 +00:00
|
|
|
fmt.Print(infoPrefix, "There are no resources in your stack (other than the stack resource).\n\n")
|
2022-09-07 14:41:26 +00:00
|
|
|
}
|
|
|
|
|
2023-02-13 22:15:13 +00:00
|
|
|
// Warn user if an update is going to leave untracked resources in the environment.
|
turn on the golangci-lint exhaustive linter (#15028)
Turn on the golangci-lint exhaustive linter. This is the first step
towards catching more missing cases during development rather than
in tests, or in production.
This might be best reviewed commit-by-commit, as the first commit turns
on the linter with the `default-signifies-exhaustive: true` option set,
which requires a lot less changes in the current codebase.
I think it's probably worth doing the second commit as well, as that
will get us the real benefits, even though we end up with a little bit
more churn. However it means all the `switch` statements are covered,
which isn't the case after the first commit, since we do have a lot of
`default` statements that just call `assert.Fail`.
Fixes #14601
## Checklist
- [x] I have run `make tidy` to update any new dependencies
- [x] I have run `make lint` to verify my code passes the lint check
- [x] I have formatted my code using `gofumpt`
<!--- Please provide details if the checkbox below is to be left
unchecked. -->
- [ ] I have added tests that prove my fix is effective or that my
feature works
<!---
User-facing changes require a CHANGELOG entry.
-->
- [ ] I have run `make changelog` and committed the
`changelog/pending/<file>` documenting my change
<!--
If the change(s) in this PR is a modification of an existing call to the
Pulumi Cloud,
then the service should honor older versions of the CLI where this
change would not exist.
You must then bump the API version in
/pkg/backend/httpstate/client/api.go, as well as add
it to the service.
-->
- [ ] Yes, there are changes in this PR that warrants bumping the Pulumi
Cloud API version
<!-- @Pulumi employees: If yes, you must submit corresponding changes in
the service repo. -->
2024-01-17 16:50:41 +00:00
|
|
|
if (kind == apitype.UpdateUpdate || kind == apitype.PreviewUpdate || kind == apitype.DestroyUpdate) &&
|
|
|
|
len(stats.retainedResources) != 0 {
|
2023-02-13 22:15:13 +00:00
|
|
|
fmt.Printf(
|
2023-04-13 22:04:53 +00:00
|
|
|
"%sThis update will leave %d resource(s) untracked in your environment:\n",
|
|
|
|
infoPrefix, len(stats.retainedResources))
|
2023-02-13 22:15:13 +00:00
|
|
|
for _, res := range stats.retainedResources {
|
|
|
|
urn := res.URN
|
2023-04-13 22:04:53 +00:00
|
|
|
fmt.Printf(" - %s %s\n", urn.Type().DisplayName(), urn.Name())
|
2023-02-13 22:15:13 +00:00
|
|
|
}
|
|
|
|
fmt.Print("\n")
|
|
|
|
}
|
|
|
|
|
2018-09-05 14:20:25 +00:00
|
|
|
// Otherwise, ensure the user wants to proceed.
|
2023-08-30 13:18:54 +00:00
|
|
|
plan, err := confirmBeforeUpdating(kind, stack, events, plan, op.Opts)
|
2018-09-06 00:50:48 +00:00
|
|
|
close(eventsChannel)
|
2023-08-30 13:18:54 +00:00
|
|
|
return plan, changes, result.WrapIfNonNil(err)
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// confirmBeforeUpdating asks the user whether to proceed. A nil error means yes.
|
|
|
|
func confirmBeforeUpdating(kind apitype.UpdateKind, stack Stack,
|
2023-03-03 16:36:39 +00:00
|
|
|
events []engine.Event, plan *deploy.Plan, opts UpdateOptions,
|
2023-08-30 13:18:54 +00:00
|
|
|
) (*deploy.Plan, error) {
|
2018-09-05 14:20:25 +00:00
|
|
|
for {
|
2022-02-02 22:59:01 +00:00
|
|
|
var response string
|
|
|
|
|
|
|
|
surveycore.DisableColor = true
|
2022-11-08 21:29:59 +00:00
|
|
|
surveyIcons := survey.WithIcons(func(icons *survey.IconSet) {
|
|
|
|
icons.Question = survey.Icon{}
|
|
|
|
icons.SelectFocus = survey.Icon{Text: opts.Display.Color.Colorize(colors.BrightGreen + ">" + colors.Reset)}
|
|
|
|
})
|
2022-02-02 22:59:01 +00:00
|
|
|
|
2018-09-05 14:20:25 +00:00
|
|
|
choices := []string{string(yes), string(no)}
|
|
|
|
|
|
|
|
// For non-previews, we can also offer a detailed summary.
|
|
|
|
if !opts.SkipPreview {
|
|
|
|
choices = append(choices, string(details))
|
|
|
|
}
|
|
|
|
|
|
|
|
var previewWarning string
|
|
|
|
if opts.SkipPreview {
|
2018-09-19 08:40:03 +00:00
|
|
|
previewWarning = colors.SpecWarning + " without a preview" + colors.Bold
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create a prompt. If this is a refresh, we'll add some extra text so it's clear we aren't updating resources.
|
|
|
|
prompt := "\b" + opts.Display.Color.Colorize(
|
2018-09-19 08:40:03 +00:00
|
|
|
colors.SpecPrompt+fmt.Sprintf("Do you want to perform this %s%s?",
|
2020-10-14 11:51:53 +00:00
|
|
|
updateTextMap[kind].previewText, previewWarning)+colors.Reset)
|
2018-09-05 14:20:25 +00:00
|
|
|
if kind == apitype.RefreshUpdate {
|
|
|
|
prompt += "\n" +
|
2018-09-19 08:40:03 +00:00
|
|
|
opts.Display.Color.Colorize(colors.SpecImportant+
|
2022-11-08 21:31:52 +00:00
|
|
|
"No resources will be modified as part of this refresh; just your stack's state will be.\n"+
|
2018-09-05 14:20:25 +00:00
|
|
|
colors.Reset)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now prompt the user for a yes, no, or details, and then proceed accordingly.
|
2022-02-02 22:59:01 +00:00
|
|
|
if err := survey.AskOne(&survey.Select{
|
2018-09-05 14:20:25 +00:00
|
|
|
Message: prompt,
|
|
|
|
Options: choices,
|
|
|
|
Default: string(no),
|
2022-11-08 21:29:59 +00:00
|
|
|
}, &response, surveyIcons); err != nil {
|
2023-08-30 13:18:54 +00:00
|
|
|
return nil, fmt.Errorf("confirmation cancelled, not proceeding with the %s: %w", kind, err)
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if response == string(no) {
|
2023-08-30 13:18:54 +00:00
|
|
|
return nil, result.FprintBailf(os.Stdout, "confirmation declined, not proceeding with the %s", kind)
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if response == string(yes) {
|
2022-11-15 10:09:42 +00:00
|
|
|
// If we're in experimental mode always use the plan
|
|
|
|
if opts.Engine.Experimental {
|
2023-08-30 13:18:54 +00:00
|
|
|
return plan, nil
|
2022-11-15 10:09:42 +00:00
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
2018-09-05 14:20:25 +00:00
|
|
|
|
|
|
|
if response == string(details) {
|
|
|
|
diff := createDiff(kind, events, opts.Display)
|
Make a smattering of CLI UX improvements
Since I was digging around over the weekend after the change to move
away from light black, and the impact it had on less important
information showing more prominently than it used to, I took a step
back and did a deeper tidying up of things. Another side goal of this
exercise was to be a little more respectful of terminal width; when
we could say things with fewer words, I did so.
* Stylize the preview/update summary differently, so that it stands
out as a section. Also highlight the total changes with bold -- it
turns out this has a similar effect to the bright white colorization,
just without the negative effects on e.g. white terminals.
* Eliminate some verbosity in the phrasing of change summaries.
* Make all heading sections stylized consistently. This includes
the color (bright magenta) and the vertical spacing (always a newline
separating headings). We were previously inconsistent on this (e.g.,
outputs were under "---outputs---"). Now the headings are:
Previewing (etc), Diagnostics, Outputs, Resources, Duration, and Permalink.
* Fix an issue where we'd parent things to "global" until the stack
object later showed up. Now we'll simply mock up a stack resource.
* Don't show messages like "no change" or "unchanged". Prior to the
light black removal, these faded into the background of the terminal.
Now they just clutter up the display. Similar to the elision of "*"
for OpSames in a prior commit, just leave these out. Now anything
that's written is actually a meaningful status for the user to note.
* Don't show the "3 info messages," etc. summaries in the Info column
while an update is ongoing. Instead, just show the latest line. This
is more respectful of width -- I often find that the important
messages scroll off the right of my screen before this change.
For discussion:
- I actually wonder if we should eliminate the summary
altogether and always just show the latest line. Or even
blank it out. The summary feels better suited for the
Diagnostics section, and the Status concisely tells us
how a resource's update ended up (failed, succeeded, etc).
- Similarly, I question the idea of showing only the "worst"
message. I'd vote for always showing the latest, and again
leaving it to the Status column for concisely telling the
user about the final state a resource ended up in.
* Stop prepending "info: " to every stdout/stderr message. It adds
no value, clutters up the display, and worsens horizontal usage.
* Lessen the verbosity of update headline messages, so we now instead
of e.g. "Previewing update of stack 'x':", we just say
"Previewing update (x):".
* Eliminate vertical whitespace in the Diagnostics section. Every
independent console.out previously was separated by an entire newline,
which made the section look cluttered to my eyes. These are just
streams of logs, there's no reason for the extra newlines.
* Colorize the resource headers in the Diagnostic section light blue.
Note that this will change various test baselines, which I will
update next. I didn't want those in the same commit.
2018-09-24 15:31:19 +00:00
|
|
|
_, err := os.Stdout.WriteString(diff + "\n")
|
2018-09-05 14:20:25 +00:00
|
|
|
contract.IgnoreError(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func PreviewThenPromptThenExecute(ctx context.Context, kind apitype.UpdateKind, stack Stack,
|
2023-03-03 16:36:39 +00:00
|
|
|
op UpdateOperation, apply Applier,
|
|
|
|
) (sdkDisplay.ResourceChanges, result.Result) {
|
2018-09-05 14:20:25 +00:00
|
|
|
// Preview the operation to the user and ask them if they want to proceed.
|
2018-10-26 21:50:47 +00:00
|
|
|
|
|
|
|
if !op.Opts.SkipPreview {
|
2022-01-31 10:31:51 +00:00
|
|
|
// We want to run the preview with the given plan and then run the full update with the initial plan as well,
|
|
|
|
// but because plans are mutated as they're checked we need to clone it here.
|
|
|
|
// We want to use the original plan because a program could be non-deterministic and have a plan of
|
|
|
|
// operations P0, the update preview could return P1, and then the actual update could run P2, were P1 < P2 < P0.
|
|
|
|
var originalPlan *deploy.Plan
|
|
|
|
if op.Opts.Engine.Plan != nil {
|
|
|
|
originalPlan = op.Opts.Engine.Plan.Clone()
|
|
|
|
}
|
|
|
|
|
|
|
|
plan, changes, res := PreviewThenPrompt(ctx, kind, stack, op, apply)
|
2019-03-19 23:21:50 +00:00
|
|
|
if res != nil || kind == apitype.PreviewUpdate {
|
|
|
|
return changes, res
|
2018-10-26 21:50:47 +00:00
|
|
|
}
|
2022-01-31 10:31:51 +00:00
|
|
|
|
2022-11-15 10:09:42 +00:00
|
|
|
// If we had an original plan use it, else if prompt said to use the plan from Preview then use the
|
|
|
|
// newly generated plan
|
2022-01-31 10:31:51 +00:00
|
|
|
if originalPlan != nil {
|
|
|
|
op.Opts.Engine.Plan = originalPlan
|
2022-11-15 10:09:42 +00:00
|
|
|
} else if plan != nil {
|
2022-01-31 10:31:51 +00:00
|
|
|
op.Opts.Engine.Plan = plan
|
2022-10-28 16:51:42 +00:00
|
|
|
} else {
|
|
|
|
op.Opts.Engine.Plan = nil
|
2022-01-31 10:31:51 +00:00
|
|
|
}
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
2018-09-21 20:57:57 +00:00
|
|
|
// Perform the change (!DryRun) and show the cloud link to the result.
|
|
|
|
// We don't care about the events it issues, so just pass a nil channel along.
|
|
|
|
opts := ApplierOptions{
|
|
|
|
DryRun: false,
|
|
|
|
ShowLink: true,
|
|
|
|
}
|
2022-10-28 16:51:42 +00:00
|
|
|
// No need to generate a plan at this stage, there's no way for the system or user to extract the plan
|
|
|
|
// after here.
|
|
|
|
op.Opts.Engine.GeneratePlan = false
|
2022-01-31 10:31:51 +00:00
|
|
|
_, changes, res := apply(ctx, kind, stack, op, opts, nil /*events*/)
|
|
|
|
return changes, res
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
2023-02-13 22:15:13 +00:00
|
|
|
type updateStats struct {
|
|
|
|
numNonStackResources int
|
|
|
|
retainedResources []engine.StepEventMetadata
|
|
|
|
}
|
|
|
|
|
|
|
|
func computeUpdateStats(events []engine.Event) updateStats {
|
|
|
|
var stats updateStats
|
2022-09-07 14:41:26 +00:00
|
|
|
|
|
|
|
for _, e := range events {
|
|
|
|
if e.Type != engine.ResourcePreEvent {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
p, ok := e.Payload().(engine.ResourcePreEventPayload)
|
|
|
|
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-02-13 22:15:13 +00:00
|
|
|
if p.Metadata.Type.String() != "pulumi:pulumi:Stack" {
|
|
|
|
stats.numNonStackResources++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Track deleted resources that are retained.
|
|
|
|
switch p.Metadata.Op {
|
|
|
|
case deploy.OpDelete, deploy.OpReplace:
|
2023-04-13 22:04:53 +00:00
|
|
|
if old := p.Metadata.Old; old != nil && old.State != nil && old.State.RetainOnDelete {
|
2023-02-13 22:15:13 +00:00
|
|
|
stats.retainedResources = append(stats.retainedResources, p.Metadata)
|
|
|
|
}
|
2022-09-07 14:41:26 +00:00
|
|
|
}
|
|
|
|
}
|
2023-02-13 22:15:13 +00:00
|
|
|
return stats
|
2022-09-07 14:41:26 +00:00
|
|
|
}
|
|
|
|
|
2018-09-05 14:20:25 +00:00
|
|
|
func createDiff(updateKind apitype.UpdateKind, events []engine.Event, displayOpts display.Options) string {
|
|
|
|
buff := &bytes.Buffer{}
|
|
|
|
|
|
|
|
seen := make(map[resource.URN]engine.StepEventMetadata)
|
|
|
|
displayOpts.SummaryDiff = true
|
|
|
|
|
2023-10-09 18:31:17 +00:00
|
|
|
outputEventsDiff := make([]string, 0)
|
|
|
|
remediationEventsDiff := make([]string, 0)
|
2018-09-05 14:20:25 +00:00
|
|
|
for _, e := range events {
|
2022-09-01 19:09:54 +00:00
|
|
|
if e.Type == engine.SummaryEvent {
|
|
|
|
continue
|
|
|
|
}
|
2023-10-09 18:31:17 +00:00
|
|
|
|
2022-04-07 16:03:19 +00:00
|
|
|
msg := display.RenderDiffEvent(e, seen, displayOpts)
|
2022-09-01 19:09:54 +00:00
|
|
|
if msg == "" {
|
|
|
|
continue
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
2023-10-09 18:31:17 +00:00
|
|
|
|
|
|
|
// Keep track of output and remediation events separately, since we print them after the
|
|
|
|
// ordinary resource diff information.
|
2022-09-01 19:09:54 +00:00
|
|
|
if e.Type == engine.ResourceOutputsEvent {
|
|
|
|
outputEventsDiff = append(outputEventsDiff, msg)
|
|
|
|
continue
|
2023-10-09 18:31:17 +00:00
|
|
|
} else if e.Type == engine.PolicyRemediationEvent {
|
|
|
|
remediationEventsDiff = append(remediationEventsDiff, msg)
|
|
|
|
continue
|
2022-09-01 19:09:54 +00:00
|
|
|
}
|
2023-10-09 18:31:17 +00:00
|
|
|
|
2022-09-01 19:09:54 +00:00
|
|
|
_, err := buff.WriteString(msg)
|
|
|
|
contract.IgnoreError(err)
|
|
|
|
}
|
2023-10-09 18:31:17 +00:00
|
|
|
|
|
|
|
// Print resource outputs next.
|
|
|
|
if len(outputEventsDiff) > 0 {
|
|
|
|
_, err := buff.WriteString("\n")
|
2022-09-01 19:09:54 +00:00
|
|
|
contract.IgnoreError(err)
|
2023-10-09 18:31:17 +00:00
|
|
|
for _, msg := range outputEventsDiff {
|
|
|
|
_, err := buff.WriteString(msg)
|
|
|
|
contract.IgnoreError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Print policy remediations last.
|
|
|
|
if len(remediationEventsDiff) > 0 {
|
|
|
|
_, err := buff.WriteString(displayOpts.Color.Colorize(
|
|
|
|
fmt.Sprintf("\n%s Policy Remediations:%s\n", colors.SpecHeadline, colors.Reset)))
|
|
|
|
contract.IgnoreError(err)
|
|
|
|
for _, msg := range remediationEventsDiff {
|
|
|
|
_, err := buff.WriteString(msg)
|
|
|
|
contract.IgnoreError(err)
|
|
|
|
}
|
2018-09-05 14:20:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(buff.String())
|
|
|
|
}
|