github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/system/events.go (about)

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