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 }