github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/event/v2/event.go (about)

     1  /*
     2  Copyright 2021 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package v2
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"sync"
    25  
    26  	"github.com/acarl005/stripansi"
    27  	"github.com/golang/protobuf/jsonpb"
    28  	"github.com/mitchellh/go-homedir"
    29  	"google.golang.org/protobuf/types/known/timestamppb"
    30  
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util"
    33  	"github.com/GoogleContainerTools/skaffold/proto/enums"
    34  	proto "github.com/GoogleContainerTools/skaffold/proto/v2"
    35  )
    36  
    37  //nolint:golint,staticcheck
    38  const (
    39  	NotStarted = "NotStarted"
    40  	InProgress = "InProgress"
    41  	Complete   = "Complete"
    42  	Failed     = "Failed"
    43  	Info       = "Information"
    44  	Started    = "Started"
    45  	Succeeded  = "Succeeded"
    46  	Terminated = "Terminated"
    47  	Canceled   = "Canceled"
    48  )
    49  
    50  var handler = newHandler()
    51  
    52  func newHandler() *eventHandler {
    53  	h := &eventHandler{
    54  		eventChan: make(chan *proto.Event),
    55  		wait:      make(chan bool, 1),
    56  		state:     &proto.State{},
    57  	}
    58  	go func() {
    59  		for {
    60  			ev, open := <-h.eventChan
    61  			if !open {
    62  				break
    63  			}
    64  			h.handleExec(ev)
    65  		}
    66  	}()
    67  	return h
    68  }
    69  
    70  type eventHandler struct {
    71  	eventLog            []*proto.Event
    72  	logLock             sync.Mutex
    73  	applicationLogs     []*proto.Event
    74  	applicationLogsLock sync.Mutex
    75  	cfg                 Config
    76  
    77  	iteration               int
    78  	errorOnce               sync.Once
    79  	wait                    chan bool
    80  	state                   *proto.State
    81  	stateLock               sync.Mutex
    82  	eventChan               chan *proto.Event
    83  	eventListeners          []*listener
    84  	applicationLogListeners []*listener
    85  }
    86  
    87  type listener struct {
    88  	callback func(*proto.Event) error
    89  	errors   chan error
    90  	closed   bool
    91  }
    92  
    93  func GetIteration() int {
    94  	return handler.iteration
    95  }
    96  
    97  func ForEachEvent(callback func(*proto.Event) error) error {
    98  	return handler.forEachEvent(callback)
    99  }
   100  
   101  func ForEachApplicationLog(callback func(*proto.Event) error) error {
   102  	return handler.forEachApplicationLog(callback)
   103  }
   104  
   105  func (ev *eventHandler) forEachEvent(callback func(*proto.Event) error) error {
   106  	// Unblock call to `WaitForConnection()`
   107  	select {
   108  	case handler.wait <- true:
   109  	default:
   110  	}
   111  	return ev.forEach(&ev.eventListeners, &ev.eventLog, &ev.logLock, callback)
   112  }
   113  
   114  func (ev *eventHandler) forEachApplicationLog(callback func(*proto.Event) error) error {
   115  	return ev.forEach(&ev.applicationLogListeners, &ev.applicationLogs, &ev.applicationLogsLock, callback)
   116  }
   117  
   118  func (ev *eventHandler) forEach(listeners *[]*listener, log *[]*proto.Event, lock sync.Locker, callback func(*proto.Event) error) error {
   119  	listener := &listener{
   120  		callback: callback,
   121  		errors:   make(chan error),
   122  	}
   123  
   124  	lock.Lock()
   125  
   126  	oldEvents := make([]*proto.Event, len(*log))
   127  	copy(oldEvents, *log)
   128  	*listeners = append(*listeners, listener)
   129  
   130  	lock.Unlock()
   131  
   132  	for i := range oldEvents {
   133  		if err := callback(oldEvents[i]); err != nil {
   134  			// listener should maybe be closed
   135  			return err
   136  		}
   137  	}
   138  
   139  	return <-listener.errors
   140  }
   141  
   142  func Handle(event *proto.Event) error {
   143  	if event != nil {
   144  		handler.handle(event)
   145  	}
   146  	return nil
   147  }
   148  
   149  // WaitForConnection will block execution until the server receives a connection
   150  func WaitForConnection() {
   151  	<-handler.wait
   152  }
   153  
   154  func (ev *eventHandler) logEvent(event *proto.Event) {
   155  	ev.log(event, &ev.eventListeners, &ev.eventLog, &ev.logLock)
   156  }
   157  
   158  func (ev *eventHandler) logApplicationLog(event *proto.Event) {
   159  	ev.log(event, &ev.applicationLogListeners, &ev.applicationLogs, &ev.applicationLogsLock)
   160  }
   161  
   162  func (ev *eventHandler) log(event *proto.Event, listeners *[]*listener, log *[]*proto.Event, lock sync.Locker) {
   163  	lock.Lock()
   164  
   165  	for _, listener := range *listeners {
   166  		if listener.closed {
   167  			continue
   168  		}
   169  
   170  		if err := listener.callback(event); err != nil {
   171  			listener.errors <- err
   172  			listener.closed = true
   173  		}
   174  	}
   175  	*log = append(*log, event)
   176  
   177  	lock.Unlock()
   178  }
   179  
   180  // PortForwarded notifies that a remote port has been forwarded locally.
   181  func PortForwarded(localPort int32, remotePort util.IntOrString, podName, containerName, namespace string, portName string, resourceType, resourceName, address string) {
   182  	event := proto.PortForwardEvent{
   183  		TaskId:        fmt.Sprintf("%s-%d", constants.PortForward, handler.iteration),
   184  		LocalPort:     localPort,
   185  		PodName:       podName,
   186  		ContainerName: containerName,
   187  		Namespace:     namespace,
   188  		PortName:      portName,
   189  		ResourceType:  resourceType,
   190  		ResourceName:  resourceName,
   191  		Address:       address,
   192  		TargetPort: &proto.IntOrString{
   193  			Type:   int32(remotePort.Type),
   194  			IntVal: int32(remotePort.IntVal),
   195  			StrVal: remotePort.StrVal,
   196  		},
   197  	}
   198  	handler.handle(&proto.Event{
   199  		EventType: &proto.Event_PortEvent{
   200  			PortEvent: &event,
   201  		},
   202  	})
   203  }
   204  
   205  // SendErrorMessageOnce sends an error message to skaffold log events stream only once.
   206  // Use it if you want to avoid sending duplicate error messages.
   207  func SendErrorMessageOnce(task constants.Phase, subtaskID string, err error) {
   208  	handler.sendErrorMessage(task, subtaskID, err)
   209  }
   210  
   211  func (ev *eventHandler) sendErrorMessage(task constants.Phase, subtask string, err error) {
   212  	if err == nil {
   213  		return
   214  	}
   215  
   216  	ev.errorOnce.Do(func() {
   217  		ev.handleSkaffoldLogEvent(&proto.SkaffoldLogEvent{
   218  			TaskId:    fmt.Sprintf("%s-%d", task, handler.iteration),
   219  			SubtaskId: subtask,
   220  			Message:   fmt.Sprintf("%s\n", err),
   221  			Level:     enums.LogLevel_STANDARD,
   222  		})
   223  	})
   224  }
   225  
   226  func (ev *eventHandler) handle(event *proto.Event) {
   227  	event.Timestamp = timestamppb.Now()
   228  	ev.eventChan <- event
   229  	if _, ok := event.GetEventType().(*proto.Event_TerminationEvent); ok {
   230  		// close the event channel indicating there are no more events to all the receivers
   231  		close(ev.eventChan)
   232  	}
   233  }
   234  
   235  func (ev *eventHandler) handleExec(event *proto.Event) {
   236  	switch e := event.GetEventType().(type) {
   237  	case *proto.Event_ApplicationLogEvent:
   238  		ev.logApplicationLog(event)
   239  		return
   240  	case *proto.Event_BuildSubtaskEvent:
   241  		be := e.BuildSubtaskEvent
   242  		if be.Step == Build {
   243  			ev.stateLock.Lock()
   244  			ev.state.BuildState.Artifacts[be.Artifact] = be.Status
   245  			ev.stateLock.Unlock()
   246  		}
   247  	case *proto.Event_TestEvent:
   248  		te := e.TestEvent
   249  		ev.stateLock.Lock()
   250  		ev.state.TestState.Status = te.Status
   251  		ev.stateLock.Unlock()
   252  	case *proto.Event_RenderEvent:
   253  		te := e.RenderEvent
   254  		ev.stateLock.Lock()
   255  		ev.state.RenderState.Status = te.Status
   256  		ev.stateLock.Unlock()
   257  	case *proto.Event_DeploySubtaskEvent:
   258  		de := e.DeploySubtaskEvent
   259  		ev.stateLock.Lock()
   260  		ev.state.DeployState.Status = de.Status
   261  		ev.stateLock.Unlock()
   262  	case *proto.Event_PortEvent:
   263  		pe := e.PortEvent
   264  		ev.stateLock.Lock()
   265  		if ev.state.ForwardedPorts == nil {
   266  			ev.state.ForwardedPorts = map[int32]*proto.PortForwardEvent{}
   267  		}
   268  		ev.state.ForwardedPorts[pe.LocalPort] = pe
   269  		ev.stateLock.Unlock()
   270  	case *proto.Event_StatusCheckSubtaskEvent:
   271  		se := e.StatusCheckSubtaskEvent
   272  		ev.stateLock.Lock()
   273  		ev.state.StatusCheckState.Resources[se.Resource] = se.Status
   274  		ev.stateLock.Unlock()
   275  	case *proto.Event_FileSyncEvent:
   276  		fse := e.FileSyncEvent
   277  		ev.stateLock.Lock()
   278  		ev.state.FileSyncState.Status = fse.Status
   279  		ev.stateLock.Unlock()
   280  	case *proto.Event_DebuggingContainerEvent:
   281  		de := e.DebuggingContainerEvent
   282  		ev.stateLock.Lock()
   283  		switch de.Status {
   284  		case Started:
   285  			ev.state.DebuggingContainers = append(ev.state.DebuggingContainers, de)
   286  		case Terminated:
   287  			n := 0
   288  			for _, x := range ev.state.DebuggingContainers {
   289  				if x.Namespace != de.Namespace || x.PodName != de.PodName || x.ContainerName != de.ContainerName {
   290  					ev.state.DebuggingContainers[n] = x
   291  					n++
   292  				}
   293  			}
   294  			ev.state.DebuggingContainers = ev.state.DebuggingContainers[:n]
   295  		}
   296  		ev.stateLock.Unlock()
   297  	}
   298  	ev.logEvent(event)
   299  }
   300  
   301  // SaveEventsToFile saves the current event log to the filepath provided
   302  func SaveEventsToFile(fp string) error {
   303  	handler.logLock.Lock()
   304  	f, err := os.OpenFile(fp, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
   305  	if err != nil {
   306  		return fmt.Errorf("opening %s: %w", fp, err)
   307  	}
   308  	defer f.Close()
   309  	marshaller := jsonpb.Marshaler{}
   310  	for _, ev := range handler.eventLog {
   311  		contents := bytes.NewBuffer([]byte{})
   312  		if err := marshaller.Marshal(contents, ev); err != nil {
   313  			return fmt.Errorf("marshalling event: %w", err)
   314  		}
   315  		if _, err := f.WriteString(contents.String() + "\n"); err != nil {
   316  			return fmt.Errorf("writing string: %w", err)
   317  		}
   318  	}
   319  	handler.logLock.Unlock()
   320  	return nil
   321  }
   322  
   323  // SaveLastLog writes the output from the previous run to the specified filepath
   324  func SaveLastLog(fp string) error {
   325  	handler.logLock.Lock()
   326  	defer handler.logLock.Unlock()
   327  
   328  	// Create file to write logs to
   329  	fp, err := lastLogFile(fp)
   330  	if err != nil {
   331  		return fmt.Errorf("getting last log file %w", err)
   332  	}
   333  	f, err := os.OpenFile(fp, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0600)
   334  	if err != nil {
   335  		return fmt.Errorf("opening %s: %w", fp, err)
   336  	}
   337  	defer f.Close()
   338  
   339  	// Iterate over events, grabbing contents only from SkaffoldLogEvents
   340  	var contents bytes.Buffer
   341  	for _, ev := range handler.eventLog {
   342  		if sle := ev.GetSkaffoldLogEvent(); sle != nil {
   343  			// Strip ansi color sequences as this makes it easier to deal with when pasting into github issues
   344  			if _, err = contents.WriteString(stripansi.Strip(sle.Message)); err != nil {
   345  				return fmt.Errorf("writing string to temporary buffer: %w", err)
   346  			}
   347  		}
   348  	}
   349  
   350  	// Write contents of temporary buffer to file
   351  	if _, err = f.Write(contents.Bytes()); err != nil {
   352  		return fmt.Errorf("writing buffer contents to file: %w", err)
   353  	}
   354  	return nil
   355  }
   356  
   357  func lastLogFile(fp string) (string, error) {
   358  	if fp != "" {
   359  		return fp, nil
   360  	}
   361  
   362  	// last log location unspecified, use ~/.skaffold/last.log
   363  	home, err := homedir.Dir()
   364  	if err != nil {
   365  		return "", fmt.Errorf("retrieving home directory: %w", err)
   366  	}
   367  	return filepath.Join(home, constants.DefaultSkaffoldDir, "last.log"), nil
   368  }