package system import ( "context" "fmt" "io" "sort" "strings" "text/template" "time" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/completion" "github.com/docker/cli/cli/command/formatter" flagsHelper "github.com/docker/cli/cli/flags" "github.com/docker/cli/opts" "github.com/docker/cli/templates" "github.com/docker/docker/api/types/events" "github.com/spf13/cobra" ) type eventsOptions struct { since string until string filter opts.FilterOpt format string } // NewEventsCommand creates a new cobra.Command for `docker events` func NewEventsCommand(dockerCli command.Cli) *cobra.Command { options := eventsOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ Use: "events [OPTIONS]", Short: "Get real time events from the server", Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return runEvents(cmd.Context(), dockerCli, &options) }, Annotations: map[string]string{ "aliases": "docker system events, docker events", }, ValidArgsFunction: completion.NoComplete, } flags := cmd.Flags() flags.StringVar(&options.since, "since", "", "Show all events created since timestamp") flags.StringVar(&options.until, "until", "", "Stream events until this timestamp") flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now. return cmd } func runEvents(ctx context.Context, dockerCli command.Cli, options *eventsOptions) error { tmpl, err := makeTemplate(options.format) if err != nil { return cli.StatusError{ StatusCode: 64, Status: "Error parsing format: " + err.Error(), } } ctx, cancel := context.WithCancel(ctx) evts, errs := dockerCli.Client().Events(ctx, events.ListOptions{ Since: options.since, Until: options.until, Filters: options.filter.Value(), }) defer cancel() out := dockerCli.Out() for { select { case event := <-evts: if err := handleEvent(out, event, tmpl); err != nil { return err } case err := <-errs: if err == io.EOF { return nil } return err } } } func handleEvent(out io.Writer, event events.Message, tmpl *template.Template) error { if tmpl == nil { return prettyPrintEvent(out, event) } return formatEvent(out, event, tmpl) } func makeTemplate(format string) (*template.Template, error) { switch format { case "": return nil, nil case formatter.JSONFormatKey: format = formatter.JSONFormat } tmpl, err := templates.Parse(format) if err != nil { return tmpl, err } // execute the template on an empty message to validate a bad // template like "{{.badFieldString}}" return tmpl, tmpl.Execute(io.Discard, &events.Message{}) } // rfc3339NanoFixed is similar to time.RFC3339Nano, except it pads nanoseconds // zeros to maintain a fixed number of characters const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" // prettyPrintEvent prints all types of event information. // Each output includes the event type, actor id, name and action. // Actor attributes are printed at the end if the actor has any. func prettyPrintEvent(out io.Writer, event events.Message) error { if event.TimeNano != 0 { fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(rfc3339NanoFixed)) } else if event.Time != 0 { fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(rfc3339NanoFixed)) } fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID) if len(event.Actor.Attributes) > 0 { var attrs []string var keys []string for k := range event.Actor.Attributes { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { v := event.Actor.Attributes[k] attrs = append(attrs, k+"="+v) } fmt.Fprintf(out, " (%s)", strings.Join(attrs, ", ")) } fmt.Fprint(out, "\n") return nil } func formatEvent(out io.Writer, event events.Message, tmpl *template.Template) error { defer out.Write([]byte{'\n'}) return tmpl.Execute(out, event) }