github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/system/events.go (about)

     1  package system
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  	"text/template"
    10  	"time"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/events"
    14  	"github.com/khulnasoft/cli/cli"
    15  	"github.com/khulnasoft/cli/cli/command"
    16  	"github.com/khulnasoft/cli/cli/command/completion"
    17  	"github.com/khulnasoft/cli/cli/command/formatter"
    18  	flagsHelper "github.com/khulnasoft/cli/cli/flags"
    19  	"github.com/khulnasoft/cli/opts"
    20  	"github.com/khulnasoft/cli/templates"
    21  	"github.com/spf13/cobra"
    22  )
    23  
    24  type eventsOptions struct {
    25  	since  string
    26  	until  string
    27  	filter opts.FilterOpt
    28  	format string
    29  }
    30  
    31  // NewEventsCommand creates a new cobra.Command for `docker events`
    32  func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
    33  	options := eventsOptions{filter: opts.NewFilterOpt()}
    34  
    35  	cmd := &cobra.Command{
    36  		Use:   "events [OPTIONS]",
    37  		Short: "Get real time events from the server",
    38  		Args:  cli.NoArgs,
    39  		RunE: func(cmd *cobra.Command, args []string) error {
    40  			return runEvents(cmd.Context(), dockerCli, &options)
    41  		},
    42  		Annotations: map[string]string{
    43  			"aliases": "docker system events, docker events",
    44  		},
    45  		ValidArgsFunction: completion.NoComplete,
    46  	}
    47  
    48  	flags := cmd.Flags()
    49  	flags.StringVar(&options.since, "since", "", "Show all events created since timestamp")
    50  	flags.StringVar(&options.until, "until", "", "Stream events until this timestamp")
    51  	flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
    52  	flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
    53  
    54  	return cmd
    55  }
    56  
    57  func runEvents(ctx context.Context, dockerCli command.Cli, options *eventsOptions) error {
    58  	tmpl, err := makeTemplate(options.format)
    59  	if err != nil {
    60  		return cli.StatusError{
    61  			StatusCode: 64,
    62  			Status:     "Error parsing format: " + err.Error(),
    63  		}
    64  	}
    65  	ctx, cancel := context.WithCancel(ctx)
    66  	evts, errs := dockerCli.Client().Events(ctx, types.EventsOptions{
    67  		Since:   options.since,
    68  		Until:   options.until,
    69  		Filters: options.filter.Value(),
    70  	})
    71  	defer cancel()
    72  
    73  	out := dockerCli.Out()
    74  
    75  	for {
    76  		select {
    77  		case event := <-evts:
    78  			if err := handleEvent(out, event, tmpl); err != nil {
    79  				return err
    80  			}
    81  		case err := <-errs:
    82  			if err == io.EOF {
    83  				return nil
    84  			}
    85  			return err
    86  		}
    87  	}
    88  }
    89  
    90  func handleEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
    91  	if tmpl == nil {
    92  		return prettyPrintEvent(out, event)
    93  	}
    94  
    95  	return formatEvent(out, event, tmpl)
    96  }
    97  
    98  func makeTemplate(format string) (*template.Template, error) {
    99  	switch format {
   100  	case "":
   101  		return nil, nil
   102  	case formatter.JSONFormatKey:
   103  		format = formatter.JSONFormat
   104  	}
   105  	tmpl, err := templates.Parse(format)
   106  	if err != nil {
   107  		return tmpl, err
   108  	}
   109  	// execute the template on an empty message to validate a bad
   110  	// template like "{{.badFieldString}}"
   111  	return tmpl, tmpl.Execute(io.Discard, &events.Message{})
   112  }
   113  
   114  // rfc3339NanoFixed is similar to time.RFC3339Nano, except it pads nanoseconds
   115  // zeros to maintain a fixed number of characters
   116  const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
   117  
   118  // prettyPrintEvent prints all types of event information.
   119  // Each output includes the event type, actor id, name and action.
   120  // Actor attributes are printed at the end if the actor has any.
   121  func prettyPrintEvent(out io.Writer, event events.Message) error {
   122  	if event.TimeNano != 0 {
   123  		fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(rfc3339NanoFixed))
   124  	} else if event.Time != 0 {
   125  		fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(rfc3339NanoFixed))
   126  	}
   127  
   128  	fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)
   129  
   130  	if len(event.Actor.Attributes) > 0 {
   131  		var attrs []string
   132  		var keys []string
   133  		for k := range event.Actor.Attributes {
   134  			keys = append(keys, k)
   135  		}
   136  		sort.Strings(keys)
   137  		for _, k := range keys {
   138  			v := event.Actor.Attributes[k]
   139  			attrs = append(attrs, k+"="+v)
   140  		}
   141  		fmt.Fprintf(out, " (%s)", strings.Join(attrs, ", "))
   142  	}
   143  	fmt.Fprint(out, "\n")
   144  	return nil
   145  }
   146  
   147  func formatEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
   148  	defer out.Write([]byte{'\n'})
   149  	return tmpl.Execute(out, event)
   150  }