Artem Chernyshev f730252579
feat: add new event types
Add config load + validation errors and address + hostnames events.

Signed-off-by: Artem Chernyshev <artem.chernyshev@talos-systems.com>
2021-11-18 18:48:35 +03:00

155 lines
3.5 KiB
Go

// 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
}
}
}