// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package client import ( "context" "fmt" "io" "log" "sync" "time" "google.golang.org/grpc/codes" machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine" "github.com/talos-systems/talos/pkg/machinery/proto" ) // EventsOptionFunc defines the options for the Events API. type EventsOptionFunc func(opts *machineapi.EventsRequest) // WithTailEvents sets up Events API to return specified number of past events. // // If number is negative, all the available past events are returned. func WithTailEvents(number int32) EventsOptionFunc { return func(opts *machineapi.EventsRequest) { opts.TailEvents = number } } // WithTailID sets up Events API to return events with ID > TailID. func WithTailID(id string) EventsOptionFunc { return func(opts *machineapi.EventsRequest) { opts.TailId = id } } // WithTailDuration sets up Watcher to return events with timestamp >= (now - tailDuration). func WithTailDuration(dur time.Duration) EventsOptionFunc { return func(opts *machineapi.EventsRequest) { opts.TailSeconds = int32(dur / time.Second) } } // Events implements the proto.OSClient interface. func (c *Client) Events(ctx context.Context, opts ...EventsOptionFunc) (stream machineapi.MachineService_EventsClient, err error) { var req machineapi.EventsRequest for _, opt := range opts { opt(&req) } return c.MachineClient.Events(ctx, &req) } // Event as received from the API. type Event struct { Node string TypeURL string ID string Payload proto.Message } // EventsWatch wraps Events by providing more simple interface. // //nolint:gocyclo func (c *Client) EventsWatch(ctx context.Context, watchFunc func(<-chan Event), opts ...EventsOptionFunc) error { stream, err := c.Events(ctx, opts...) if err != nil { return fmt.Errorf("error fetching events: %s", err) } if err = stream.CloseSend(); err != nil { return err } defaultNode := RemotePeer(stream.Context()) //nolint:contextcheck var wg sync.WaitGroup defer wg.Wait() ch := make(chan Event) defer close(ch) wg.Add(1) go func() { defer wg.Done() watchFunc(ch) }() for { event, err := stream.Recv() if err != nil { if err == io.EOF || StatusCode(err) == codes.Canceled { return nil } return fmt.Errorf("failed to watch events: %w", err) } typeURL := event.GetData().GetTypeUrl() var msg proto.Message for _, eventType := range []proto.Message{ &machineapi.SequenceEvent{}, &machineapi.PhaseEvent{}, &machineapi.TaskEvent{}, &machineapi.ServiceStateEvent{}, &machineapi.ConfigLoadErrorEvent{}, &machineapi.ConfigValidationErrorEvent{}, &machineapi.AddressEvent{}, } { if typeURL == "talos/runtime/"+string(eventType.ProtoReflect().Descriptor().FullName()) { msg = eventType break } } if msg == nil { // We haven't implemented the handling of this event yet. continue } if err = proto.Unmarshal(event.GetData().GetValue(), msg); err != nil { log.Printf("failed to unmarshal message: %v", err) // TODO: this should be fixed to return errors continue } ev := Event{ Node: defaultNode, TypeURL: typeURL, ID: event.Id, Payload: msg, } if event.Metadata != nil { ev.Node = event.Metadata.Hostname } select { case ch <- ev: case <-ctx.Done(): return nil } } }