pulumi/pkg/backend/display/watch.go

131 lines
4.2 KiB
Go

// Copyright 2016-2019, 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 display
import (
"bytes"
"fmt"
"io"
"os"
"sync"
"time"
"github.com/pulumi/pulumi/pkg/v3/engine"
"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// We use RFC 5424 timestamps with millisecond precision for displaying time stamps on watch
// entries. Go does not pre-define a format string for this format, though it is similar to
// time.RFC3339Nano.
//
// See https://tools.ietf.org/html/rfc5424#section-6.2.3.
const timeFormat = "15:04:05.000"
// ShowWatchEvents renders incoming engine events for display in Watch Mode.
func ShowWatchEvents(op string, action apitype.UpdateKind, events <-chan engine.Event, done chan<- bool, opts Options) {
// Ensure we close the done channel before exiting.
defer func() { close(done) }()
for e := range events {
// In the event of cancelation, break out of the loop immediately.
if e.Type == engine.CancelEvent {
break
}
// For all other events, use the payload to build up the JSON digest we'll emit later.
switch e.Type {
// Events occurring early:
case engine.PreludeEvent, engine.SummaryEvent, engine.StdoutColorEvent:
// Ignore it
continue
case engine.PolicyViolationEvent:
// At this point in time, we don't handle policy events as part of pulumi watch
continue
case engine.DiagEvent:
// Skip any ephemeral or debug messages, and elide all colorization.
p := e.Payload().(engine.DiagEventPayload)
resourceName := ""
if p.URN != "" {
resourceName = string(p.URN.Name())
}
PrintfWithWatchPrefix(time.Now(), resourceName,
"%s", renderDiffDiagEvent(p, opts))
case engine.ResourcePreEvent:
p := e.Payload().(engine.ResourcePreEventPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"%s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
case engine.ResourceOutputsEvent:
p := e.Payload().(engine.ResourceOutputsEventPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"done %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
case engine.ResourceOperationFailed:
p := e.Payload().(engine.ResourceOperationFailedPayload)
if shouldShow(p.Metadata, opts) {
PrintfWithWatchPrefix(time.Now(), string(p.Metadata.URN.Name()),
"failed %s %s\n", p.Metadata.Op, p.Metadata.URN.Type())
}
default:
contract.Failf("unknown event type '%s'", e.Type)
}
}
}
// Watch output is written from multiple concurrent goroutines. For now we synchronize Printfs to
// the watch output stream as a simple way to avoid garbled output.
var watchPrintfMutex sync.Mutex
// PrintfWithWatchPrefix wraps fmt.Printf with a watch mode prefixer that adds a timestamp and
// resource metadata.
func PrintfWithWatchPrefix(t time.Time, resourceName string, format string, a ...interface{}) {
watchPrintfMutex.Lock()
defer watchPrintfMutex.Unlock()
prefix := fmt.Sprintf("%12.12s[%20.20s] ", t.Format(timeFormat), resourceName)
out := &prefixer{os.Stdout, []byte(prefix)}
_, err := fmt.Fprintf(out, format, a...)
contract.IgnoreError(err)
}
type prefixer struct {
writer io.Writer
prefix []byte
}
var _ io.Writer = (*prefixer)(nil)
func (prefixer *prefixer) Write(p []byte) (int, error) {
n := 0
lines := bytes.SplitAfter(p, []byte{'\n'})
for _, line := range lines {
// If p ends with a newline, we may see an "" as the last element of lines, which we will skip.
if len(line) == 0 {
continue
}
_, err := prefixer.writer.Write(prefixer.prefix)
if err != nil {
return n, err
}
m, err := prefixer.writer.Write(line)
n += m
if err != nil {
return n, err
}
}
return n, nil
}