github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/backend/display/display.go (about)

     1  // Copyright 2016-2018, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package display
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"time"
    23  
    24  	"github.com/pulumi/pulumi/pkg/v3/engine"
    25  	"github.com/pulumi/pulumi/pkg/v3/resource/deploy"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    27  	"github.com/pulumi/pulumi/sdk/v3/go/common/diag/colors"
    28  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    29  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/cmdutil"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/logging"
    33  )
    34  
    35  // ShowEvents reads events from the `events` channel until it is closed, displaying each event as
    36  // it comes in. Once all events have been read from the channel and displayed, it closes the `done`
    37  // channel so the caller can await all the events being written.
    38  func ShowEvents(
    39  	op string, action apitype.UpdateKind, stack tokens.Name, proj tokens.PackageName,
    40  	events <-chan engine.Event, done chan<- bool, opts Options, isPreview bool) {
    41  
    42  	if opts.EventLogPath != "" {
    43  		events, done = startEventLogger(events, done, opts)
    44  	}
    45  
    46  	streamPreview := cmdutil.IsTruthy(os.Getenv("PULUMI_ENABLE_STREAMING_JSON_PREVIEW"))
    47  
    48  	if opts.JSONDisplay {
    49  		if isPreview && !streamPreview {
    50  			ShowPreviewDigest(events, done, opts)
    51  		} else {
    52  			ShowJSONEvents(events, done, opts)
    53  		}
    54  		return
    55  	}
    56  
    57  	switch opts.Type {
    58  	case DisplayDiff:
    59  		ShowDiffEvents(op, events, done, opts)
    60  	case DisplayProgress:
    61  		ShowProgressEvents(op, action, stack, proj, events, done, opts, isPreview)
    62  	case DisplayQuery:
    63  		contract.Failf("DisplayQuery can only be used in query mode, which should be invoked " +
    64  			"directly instead of through ShowEvents")
    65  	case DisplayWatch:
    66  		ShowWatchEvents(op, events, done, opts)
    67  	default:
    68  		contract.Failf("Unknown display type %d", opts.Type)
    69  	}
    70  }
    71  
    72  func logJSONEvent(encoder *json.Encoder, event engine.Event, opts Options, seq int) error {
    73  	apiEvent, err := ConvertEngineEvent(event, false /* showSecrets */)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	apiEvent.Sequence = seq
    79  	apiEvent.Timestamp = int(time.Now().Unix())
    80  	// If opts.Color == "never" (i.e. NO_COLOR is specified or --color=never), clean up the color directives
    81  	// from the emitted events.
    82  	if opts.Color == colors.Never {
    83  		switch {
    84  		case apiEvent.DiagnosticEvent != nil:
    85  			apiEvent.DiagnosticEvent.Message = colors.Never.Colorize(apiEvent.DiagnosticEvent.Message)
    86  			apiEvent.DiagnosticEvent.Prefix = colors.Never.Colorize(apiEvent.DiagnosticEvent.Prefix)
    87  			apiEvent.DiagnosticEvent.Color = string(colors.Never)
    88  		case apiEvent.StdoutEvent != nil:
    89  			apiEvent.StdoutEvent.Message = colors.Never.Colorize(apiEvent.StdoutEvent.Message)
    90  			apiEvent.StdoutEvent.Color = string(colors.Never)
    91  		case apiEvent.PolicyEvent != nil:
    92  			apiEvent.PolicyEvent.Message = colors.Never.Colorize(apiEvent.PolicyEvent.Message)
    93  			apiEvent.PolicyEvent.Color = string(colors.Never)
    94  		}
    95  	}
    96  
    97  	return encoder.Encode(apiEvent)
    98  }
    99  
   100  func startEventLogger(events <-chan engine.Event, done chan<- bool, opts Options) (<-chan engine.Event, chan<- bool) {
   101  	// Before moving further, attempt to open the log file.
   102  	logFile, err := os.Create(opts.EventLogPath)
   103  	if err != nil {
   104  		logging.V(7).Infof("could not create event log: %v", err)
   105  		return events, done
   106  	}
   107  
   108  	outEvents, outDone := make(chan engine.Event), make(chan bool)
   109  	go func() {
   110  		defer close(done)
   111  		defer func() {
   112  			contract.IgnoreError(logFile.Close())
   113  		}()
   114  
   115  		sequence := 0
   116  		encoder := json.NewEncoder(logFile)
   117  		encoder.SetEscapeHTML(false)
   118  		for e := range events {
   119  			if err = logJSONEvent(encoder, e, opts, sequence); err != nil {
   120  				logging.V(7).Infof("failed to log event: %v", err)
   121  			}
   122  			sequence++
   123  
   124  			outEvents <- e
   125  
   126  			if e.Type == engine.CancelEvent {
   127  				break
   128  			}
   129  		}
   130  
   131  		<-outDone
   132  	}()
   133  
   134  	return outEvents, outDone
   135  }
   136  
   137  type nopSpinner struct {
   138  }
   139  
   140  func (s *nopSpinner) Tick() {
   141  }
   142  
   143  func (s *nopSpinner) Reset() {
   144  }
   145  
   146  // isRootStack returns true if the step pertains to the rootmost stack component.
   147  func isRootStack(step engine.StepEventMetadata) bool {
   148  	return isRootURN(step.URN)
   149  }
   150  
   151  func isRootURN(urn resource.URN) bool {
   152  	return urn != "" && urn.Type() == resource.RootStackType
   153  }
   154  
   155  // shouldShow returns true if a step should show in the output.
   156  func shouldShow(step engine.StepEventMetadata, opts Options) bool {
   157  	// For certain operations, whether they are tracked is controlled by flags (to cut down on superfluous output).
   158  	if step.Op == deploy.OpSame {
   159  		// If the op is the same, it is possible that the resource's metadata changed.  In that case, still show it.
   160  		if step.Old.Protect != step.New.Protect {
   161  			return true
   162  		}
   163  		return opts.ShowSameResources
   164  	}
   165  
   166  	// For logical replacement operations, only show them during progress-style updates (since this is integrated
   167  	// into the resource status update), or if it is requested explicitly (for diffs and JSON outputs).
   168  	if (opts.Type == DisplayDiff || opts.JSONDisplay) && !step.Logical && !opts.ShowReplacementSteps {
   169  		return false
   170  	}
   171  
   172  	// Otherwise, default to showing the operation.
   173  	return true
   174  }
   175  
   176  func fprintfIgnoreError(w io.Writer, format string, a ...interface{}) {
   177  	_, err := fmt.Fprintf(w, format, a...)
   178  	contract.IgnoreError(err)
   179  }
   180  
   181  func fprintIgnoreError(w io.Writer, a ...interface{}) {
   182  	_, err := fmt.Fprint(w, a...)
   183  	contract.IgnoreError(err)
   184  }