diff --git a/cli/command/events_utils.go b/cli/command/events_utils.go
index 16d76892a1..6a2907c531 100644
--- a/cli/command/events_utils.go
+++ b/cli/command/events_utils.go
@@ -3,28 +3,28 @@ package command
import (
"sync"
- eventtypes "github.com/docker/docker/api/types/events"
+ "github.com/docker/docker/api/types/events"
"github.com/sirupsen/logrus"
)
// EventHandler is abstract interface for user to customize
// own handle functions of each type of events
type EventHandler interface {
- Handle(action string, h func(eventtypes.Message))
- Watch(c <-chan eventtypes.Message)
+ Handle(action string, h func(events.Message))
+ Watch(c <-chan events.Message)
}
// InitEventHandler initializes and returns an EventHandler
func InitEventHandler() EventHandler {
- return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
+ return &eventHandler{handlers: make(map[string]func(events.Message))}
}
type eventHandler struct {
- handlers map[string]func(eventtypes.Message)
+ handlers map[string]func(events.Message)
mu sync.Mutex
}
-func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
+func (w *eventHandler) Handle(action string, h func(events.Message)) {
w.mu.Lock()
w.handlers[action] = h
w.mu.Unlock()
@@ -33,7 +33,7 @@ func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
// Watch ranges over the passed in event chan and processes the events based on the
// handlers created for a given action.
// To stop watching, close the event chan.
-func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
+func (w *eventHandler) Watch(c <-chan events.Message) {
for e := range c {
w.mu.Lock()
h, exists := w.handlers[e.Action]
diff --git a/cli/command/system/client_test.go b/cli/command/system/client_test.go
index 20d8dc38cc..a275426fe1 100644
--- a/cli/command/system/client_test.go
+++ b/cli/command/system/client_test.go
@@ -4,6 +4,7 @@ import (
"context"
"github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/events"
"github.com/docker/docker/client"
)
@@ -12,6 +13,7 @@ type fakeClient struct {
version string
serverVersion func(ctx context.Context) (types.Version, error)
+ eventsFn func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
}
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
@@ -21,3 +23,7 @@ func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error)
func (cli *fakeClient) ClientVersion() string {
return cli.version
}
+
+func (cli *fakeClient) Events(ctx context.Context, opts types.EventsOptions) (<-chan events.Message, <-chan error) {
+ return cli.eventsFn(ctx, opts)
+}
diff --git a/cli/command/system/events.go b/cli/command/system/events.go
index ddac0fa0a0..2c6cd3df5b 100644
--- a/cli/command/system/events.go
+++ b/cli/command/system/events.go
@@ -12,10 +12,12 @@ import (
"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"
- eventtypes "github.com/docker/docker/api/types/events"
+ "github.com/docker/docker/api/types/events"
"github.com/spf13/cobra"
)
@@ -47,7 +49,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
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", "", "Format the output using the given Go template")
+ flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
return cmd
}
@@ -60,21 +62,19 @@ func runEvents(dockerCli command.Cli, options *eventsOptions) error {
Status: "Error parsing format: " + err.Error(),
}
}
- eventOptions := types.EventsOptions{
+ ctx, cancel := context.WithCancel(context.Background())
+ evts, errs := dockerCli.Client().Events(ctx, types.EventsOptions{
Since: options.since,
Until: options.until,
Filters: options.filter.Value(),
- }
-
- ctx, cancel := context.WithCancel(context.Background())
- events, errs := dockerCli.Client().Events(ctx, eventOptions)
+ })
defer cancel()
out := dockerCli.Out()
for {
select {
- case event := <-events:
+ case event := <-evts:
if err := handleEvent(out, event, tmpl); err != nil {
return err
}
@@ -87,7 +87,7 @@ func runEvents(dockerCli command.Cli, options *eventsOptions) error {
}
}
-func handleEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
+func handleEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
if tmpl == nil {
return prettyPrintEvent(out, event)
}
@@ -96,16 +96,19 @@ func handleEvent(out io.Writer, event eventtypes.Message, tmpl *template.Templat
}
func makeTemplate(format string) (*template.Template, error) {
- if format == "" {
+ switch format {
+ case "":
return nil, nil
+ case formatter.JSONFormatKey:
+ format = formatter.JSONFormat
}
tmpl, err := templates.Parse(format)
if err != nil {
return tmpl, err
}
- // we execute the template for an empty message, so as to validate
- // a bad template like "{{.badFieldString}}"
- return tmpl, tmpl.Execute(io.Discard, &eventtypes.Message{})
+ // 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
@@ -115,7 +118,7 @@ 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 eventtypes.Message) error {
+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 {
@@ -141,7 +144,7 @@ func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
return nil
}
-func formatEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
+func formatEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
defer out.Write([]byte{'\n'})
return tmpl.Execute(out, event)
}
diff --git a/cli/command/system/events_test.go b/cli/command/system/events_test.go
new file mode 100644
index 0000000000..1112edd895
--- /dev/null
+++ b/cli/command/system/events_test.go
@@ -0,0 +1,83 @@
+package system
+
+import (
+ "context"
+ "fmt"
+ "io"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/docker/cli/internal/test"
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/events"
+ "gotest.tools/v3/assert"
+ "gotest.tools/v3/golden"
+)
+
+func TestEventsFormat(t *testing.T) {
+ var evts []events.Message
+ for i, action := range []string{"create", "start", "attach", "die"} {
+ evts = append(evts, events.Message{
+ Status: action,
+ ID: "abc123",
+ From: "ubuntu:latest",
+ Type: events.ContainerEventType,
+ Action: action,
+ Actor: events.Actor{
+ ID: "abc123",
+ Attributes: map[string]string{"image": "ubuntu:latest"},
+ },
+ Scope: "local",
+ Time: int64(time.Second) * int64(i+1),
+ TimeNano: int64(time.Second) * int64(i+1),
+ })
+ }
+ tests := []struct {
+ name, format string
+ }{
+ {
+ name: "default",
+ },
+ {
+ name: "json",
+ format: "json",
+ },
+ {
+ name: "json template",
+ format: "{{ json . }}",
+ },
+ {
+ name: "json action",
+ format: "{{ json .Action }}",
+ },
+ }
+
+ for _, tc := range tests {
+ tc := tc
+ t.Run(tc.name, func(t *testing.T) {
+ // Set to UTC timezone as timestamps in output are
+ // printed in the current timezone
+ t.Setenv("TZ", "UTC")
+ cli := test.NewFakeCli(&fakeClient{eventsFn: func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error) {
+ messages := make(chan events.Message)
+ errs := make(chan error, 1)
+ go func() {
+ for _, msg := range evts {
+ messages <- msg
+ }
+ errs <- io.EOF
+ }()
+ return messages, errs
+ }})
+ cmd := NewEventsCommand(cli)
+ if tc.format != "" {
+ cmd.Flags().Set("format", tc.format)
+ }
+ assert.Check(t, cmd.Execute())
+ out := cli.OutBuffer().String()
+ assert.Check(t, golden.String(out, fmt.Sprintf("docker-events-%s.golden", strings.ReplaceAll(tc.name, " ", "-"))))
+ cli.OutBuffer().Reset()
+ })
+ }
+}
diff --git a/cli/command/system/testdata/docker-events-default.golden b/cli/command/system/testdata/docker-events-default.golden
new file mode 100644
index 0000000000..931a7e79c3
--- /dev/null
+++ b/cli/command/system/testdata/docker-events-default.golden
@@ -0,0 +1,4 @@
+1970-01-01T00:00:01.000000000Z container create abc123 (image=ubuntu:latest)
+1970-01-01T00:00:02.000000000Z container start abc123 (image=ubuntu:latest)
+1970-01-01T00:00:03.000000000Z container attach abc123 (image=ubuntu:latest)
+1970-01-01T00:00:04.000000000Z container die abc123 (image=ubuntu:latest)
diff --git a/cli/command/system/testdata/docker-events-json-action.golden b/cli/command/system/testdata/docker-events-json-action.golden
new file mode 100644
index 0000000000..467383ae4b
--- /dev/null
+++ b/cli/command/system/testdata/docker-events-json-action.golden
@@ -0,0 +1,4 @@
+"create"
+"start"
+"attach"
+"die"
diff --git a/cli/command/system/testdata/docker-events-json-template.golden b/cli/command/system/testdata/docker-events-json-template.golden
new file mode 100644
index 0000000000..ec5343fe68
--- /dev/null
+++ b/cli/command/system/testdata/docker-events-json-template.golden
@@ -0,0 +1,4 @@
+{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000}
+{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000}
+{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000}
+{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000}
diff --git a/cli/command/system/testdata/docker-events-json.golden b/cli/command/system/testdata/docker-events-json.golden
new file mode 100644
index 0000000000..ec5343fe68
--- /dev/null
+++ b/cli/command/system/testdata/docker-events-json.golden
@@ -0,0 +1,4 @@
+{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000}
+{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000}
+{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000}
+{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000}
diff --git a/docs/reference/commandline/events.md b/docs/reference/commandline/events.md
index 44baef0449..56ca197596 100644
--- a/docs/reference/commandline/events.md
+++ b/docs/reference/commandline/events.md
@@ -9,12 +9,12 @@ Get real time events from the server
### Options
-| Name | Type | Default | Description |
-|:---------------------------------------|:---------|:--------|:----------------------------------------------|
-| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
-| [`--format`](#format) | `string` | | Format the output using the given Go template |
-| [`--since`](#since) | `string` | | Show all events created since timestamp |
-| `--until` | `string` | | Stream events until this timestamp |
+| Name | Type | Default | Description |
+|:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
+| [`--format`](#format) | `string` | | Format output using a custom template:
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
+| [`--since`](#since) | `string` | | Show all events created since timestamp |
+| `--until` | `string` | | Stream events until this timestamp |
@@ -401,8 +401,11 @@ Type=container Status=destroy ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299
#### Format as JSON
+To list events in JSON format, use the `json` directive, which is the equivalent
+of `--format '{{ json . }}`.
+
```console
-$ docker events --format '{{json .}}'
+$ docker events --format json
{"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
{"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
@@ -410,3 +413,5 @@ $ docker events --format '{{json .}}'
{"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
{"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
```
+
+.
diff --git a/docs/reference/commandline/system_events.md b/docs/reference/commandline/system_events.md
index e580f4b9c8..601d4c6e30 100644
--- a/docs/reference/commandline/system_events.md
+++ b/docs/reference/commandline/system_events.md
@@ -9,12 +9,12 @@ Get real time events from the server
### Options
-| Name | Type | Default | Description |
-|:---------------------------------------|:---------|:--------|:----------------------------------------------|
-| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
-| [`--format`](#format) | `string` | | Format the output using the given Go template |
-| `--since` | `string` | | Show all events created since timestamp |
-| `--until` | `string` | | Stream events until this timestamp |
+| Name | Type | Default | Description |
+|:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
+| [`--format`](#format) | `string` | | Format output using a custom template:
'json': Print in JSON format
'TEMPLATE': Print output using the given Go template.
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
+| `--since` | `string` | | Show all events created since timestamp |
+| `--until` | `string` | | Stream events until this timestamp |