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

     1  // Copyright 2016-2019, 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  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/pulumi/pulumi/pkg/v3/engine"
    26  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    27  )
    28  
    29  // We use RFC 5424 timestamps with millisecond precision for displaying time stamps on watch
    30  // entries. Go does not pre-define a format string for this format, though it is similar to
    31  // time.RFC3339Nano.
    32  //
    33  // See https://tools.ietf.org/html/rfc5424#section-6.2.3.
    34  const timeFormat = "15:04:05.000"
    35  
    36  // ShowWatchEvents renders incoming engine events for display in Watch Mode.
    37  func ShowWatchEvents(op string, events <-chan engine.Event, done chan<- bool, opts Options) {
    38  	// Ensure we close the done channel before exiting.
    39  	defer func() { close(done) }()
    40  	for e := range events {
    41  		// In the event of cancelation, break out of the loop immediately.
    42  		if e.Type == engine.CancelEvent {
    43  			break
    44  		}
    45  
    46  		// For all other events, use the payload to build up the JSON digest we'll emit later.
    47  		switch e.Type {
    48  		// Events occurring early:
    49  		case engine.PreludeEvent, engine.SummaryEvent, engine.StdoutColorEvent:
    50  			// Ignore it
    51  			continue
    52  		case engine.PolicyViolationEvent:
    53  			// At this point in time, we don't handle policy events as part of pulumi watch
    54  			continue
    55  		case engine.DiagEvent:
    56  			// Skip any ephemeral or debug messages, and elide all colorization.
    57  			p := e.Payload().(engine.DiagEventPayload)
    58  			resourceName := ""
    59  			if p.URN != "" {
    60  				resourceName = string(p.URN.Name())
    61  			}
    62  			PrintfWithWatchPrefix(time.Now(), resourceName,
    63  				"%s", renderDiffDiagEvent(p, opts))
    64  		case engine.ResourcePreEvent:
    65  			p := e.Payload().(engine.ResourcePreEventPayload)
    66  			if shouldShow(p.Metadata, opts) {
    67  				PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
    68  					"%s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
    69  			}
    70  		case engine.ResourceOutputsEvent:
    71  			p := e.Payload().(engine.ResourceOutputsEventPayload)
    72  			if shouldShow(p.Metadata, opts) {
    73  				PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
    74  					"done %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
    75  			}
    76  		case engine.ResourceOperationFailed:
    77  			p := e.Payload().(engine.ResourceOperationFailedPayload)
    78  			if shouldShow(p.Metadata, opts) {
    79  				PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
    80  					"failed %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
    81  			}
    82  		default:
    83  			contract.Failf("unknown event type '%s'", e.Type)
    84  		}
    85  	}
    86  }
    87  
    88  // Watch output is written from multiple concurrent goroutines.  For now we synchronize Printfs to
    89  // the watch output stream as a simple way to avoid garbled output.
    90  var watchPrintfMutex sync.Mutex
    91  
    92  // PrintfWithWatchPrefix wraps fmt.Printf with a watch mode prefixer that adds a timestamp and
    93  // resource metadata.
    94  func PrintfWithWatchPrefix(t time.Time, resourceName string, format string, a ...interface{}) {
    95  	watchPrintfMutex.Lock()
    96  	defer watchPrintfMutex.Unlock()
    97  	prefix := fmt.Sprintf("%12.12s[%20.20s] ", t.Format(timeFormat), resourceName)
    98  	out := &prefixer{os.Stdout, []byte(prefix)}
    99  	_, err := fmt.Fprintf(out, format, a...)
   100  	contract.IgnoreError(err)
   101  }
   102  
   103  type prefixer struct {
   104  	writer io.Writer
   105  	prefix []byte
   106  }
   107  
   108  var _ io.Writer = (*prefixer)(nil)
   109  
   110  func (prefixer *prefixer) Write(p []byte) (int, error) {
   111  	n := 0
   112  	lines := bytes.SplitAfter(p, []byte{'\n'})
   113  	for _, line := range lines {
   114  		// If p ends with a newline, we may see an "" as the last element of lines, which we will skip.
   115  		if len(line) == 0 {
   116  			continue
   117  		}
   118  		_, err := prefixer.writer.Write(prefixer.prefix)
   119  		if err != nil {
   120  			return n, err
   121  		}
   122  		m, err := prefixer.writer.Write(line)
   123  		n += m
   124  		if err != nil {
   125  			return n, err
   126  		}
   127  	}
   128  	return n, nil
   129  }