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 }