Skip to content

Commit f74f884

Browse files
authored
Merge pull request #4530 from thaJeztah/fix_events_json_format
cli/command/system: fix "docker events" not supporting --format=json
2 parents a1367a0 + 6dfdd1e commit f74f884

File tree

10 files changed

+148
-35
lines changed

10 files changed

+148
-35
lines changed

cli/command/events_utils.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ package command
33
import (
44
"sync"
55

6-
eventtypes "github.com/docker/docker/api/types/events"
6+
"github.com/docker/docker/api/types/events"
77
"github.com/sirupsen/logrus"
88
)
99

1010
// EventHandler is abstract interface for user to customize
1111
// own handle functions of each type of events
1212
type EventHandler interface {
13-
Handle(action string, h func(eventtypes.Message))
14-
Watch(c <-chan eventtypes.Message)
13+
Handle(action string, h func(events.Message))
14+
Watch(c <-chan events.Message)
1515
}
1616

1717
// InitEventHandler initializes and returns an EventHandler
1818
func InitEventHandler() EventHandler {
19-
return &eventHandler{handlers: make(map[string]func(eventtypes.Message))}
19+
return &eventHandler{handlers: make(map[string]func(events.Message))}
2020
}
2121

2222
type eventHandler struct {
23-
handlers map[string]func(eventtypes.Message)
23+
handlers map[string]func(events.Message)
2424
mu sync.Mutex
2525
}
2626

27-
func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
27+
func (w *eventHandler) Handle(action string, h func(events.Message)) {
2828
w.mu.Lock()
2929
w.handlers[action] = h
3030
w.mu.Unlock()
@@ -33,7 +33,7 @@ func (w *eventHandler) Handle(action string, h func(eventtypes.Message)) {
3333
// Watch ranges over the passed in event chan and processes the events based on the
3434
// handlers created for a given action.
3535
// To stop watching, close the event chan.
36-
func (w *eventHandler) Watch(c <-chan eventtypes.Message) {
36+
func (w *eventHandler) Watch(c <-chan events.Message) {
3737
for e := range c {
3838
w.mu.Lock()
3939
h, exists := w.handlers[e.Action]

cli/command/system/client_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55

66
"github.com/docker/docker/api/types"
7+
"github.com/docker/docker/api/types/events"
78
"github.com/docker/docker/client"
89
)
910

@@ -12,6 +13,7 @@ type fakeClient struct {
1213

1314
version string
1415
serverVersion func(ctx context.Context) (types.Version, error)
16+
eventsFn func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
1517
}
1618

1719
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
@@ -21,3 +23,7 @@ func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error)
2123
func (cli *fakeClient) ClientVersion() string {
2224
return cli.version
2325
}
26+
27+
func (cli *fakeClient) Events(ctx context.Context, opts types.EventsOptions) (<-chan events.Message, <-chan error) {
28+
return cli.eventsFn(ctx, opts)
29+
}

cli/command/system/events.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import (
1212
"github.com/docker/cli/cli"
1313
"github.com/docker/cli/cli/command"
1414
"github.com/docker/cli/cli/command/completion"
15+
"github.com/docker/cli/cli/command/formatter"
16+
flagsHelper "github.com/docker/cli/cli/flags"
1517
"github.com/docker/cli/opts"
1618
"github.com/docker/cli/templates"
1719
"github.com/docker/docker/api/types"
18-
eventtypes "github.com/docker/docker/api/types/events"
20+
"github.com/docker/docker/api/types/events"
1921
"github.com/spf13/cobra"
2022
)
2123

@@ -47,7 +49,7 @@ func NewEventsCommand(dockerCli command.Cli) *cobra.Command {
4749
flags.StringVar(&options.since, "since", "", "Show all events created since timestamp")
4850
flags.StringVar(&options.until, "until", "", "Stream events until this timestamp")
4951
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
50-
flags.StringVar(&options.format, "format", "", "Format the output using the given Go template")
52+
flags.StringVar(&options.format, "format", "", flagsHelper.InspectFormatHelp) // using the same flag description as "inspect" commands for now.
5153

5254
return cmd
5355
}
@@ -60,21 +62,19 @@ func runEvents(dockerCli command.Cli, options *eventsOptions) error {
6062
Status: "Error parsing format: " + err.Error(),
6163
}
6264
}
63-
eventOptions := types.EventsOptions{
65+
ctx, cancel := context.WithCancel(context.Background())
66+
evts, errs := dockerCli.Client().Events(ctx, types.EventsOptions{
6467
Since: options.since,
6568
Until: options.until,
6669
Filters: options.filter.Value(),
67-
}
68-
69-
ctx, cancel := context.WithCancel(context.Background())
70-
events, errs := dockerCli.Client().Events(ctx, eventOptions)
70+
})
7171
defer cancel()
7272

7373
out := dockerCli.Out()
7474

7575
for {
7676
select {
77-
case event := <-events:
77+
case event := <-evts:
7878
if err := handleEvent(out, event, tmpl); err != nil {
7979
return err
8080
}
@@ -87,7 +87,7 @@ func runEvents(dockerCli command.Cli, options *eventsOptions) error {
8787
}
8888
}
8989

90-
func handleEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
90+
func handleEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
9191
if tmpl == nil {
9292
return prettyPrintEvent(out, event)
9393
}
@@ -96,16 +96,19 @@ func handleEvent(out io.Writer, event eventtypes.Message, tmpl *template.Templat
9696
}
9797

9898
func makeTemplate(format string) (*template.Template, error) {
99-
if format == "" {
99+
switch format {
100+
case "":
100101
return nil, nil
102+
case formatter.JSONFormatKey:
103+
format = formatter.JSONFormat
101104
}
102105
tmpl, err := templates.Parse(format)
103106
if err != nil {
104107
return tmpl, err
105108
}
106-
// we execute the template for an empty message, so as to validate
107-
// a bad template like "{{.badFieldString}}"
108-
return tmpl, tmpl.Execute(io.Discard, &eventtypes.Message{})
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{})
109112
}
110113

111114
// rfc3339NanoFixed is similar to time.RFC3339Nano, except it pads nanoseconds
@@ -115,7 +118,7 @@ const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
115118
// prettyPrintEvent prints all types of event information.
116119
// Each output includes the event type, actor id, name and action.
117120
// Actor attributes are printed at the end if the actor has any.
118-
func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
121+
func prettyPrintEvent(out io.Writer, event events.Message) error {
119122
if event.TimeNano != 0 {
120123
fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(rfc3339NanoFixed))
121124
} else if event.Time != 0 {
@@ -141,7 +144,7 @@ func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
141144
return nil
142145
}
143146

144-
func formatEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
147+
func formatEvent(out io.Writer, event events.Message, tmpl *template.Template) error {
145148
defer out.Write([]byte{'\n'})
146149
return tmpl.Execute(out, event)
147150
}

cli/command/system/events_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package system
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"strings"
8+
"testing"
9+
"time"
10+
11+
"github.com/docker/cli/internal/test"
12+
"github.com/docker/docker/api/types"
13+
"github.com/docker/docker/api/types/events"
14+
"gotest.tools/v3/assert"
15+
"gotest.tools/v3/golden"
16+
)
17+
18+
func TestEventsFormat(t *testing.T) {
19+
var evts []events.Message
20+
for i, action := range []string{"create", "start", "attach", "die"} {
21+
evts = append(evts, events.Message{
22+
Status: action,
23+
ID: "abc123",
24+
From: "ubuntu:latest",
25+
Type: events.ContainerEventType,
26+
Action: action,
27+
Actor: events.Actor{
28+
ID: "abc123",
29+
Attributes: map[string]string{"image": "ubuntu:latest"},
30+
},
31+
Scope: "local",
32+
Time: int64(time.Second) * int64(i+1),
33+
TimeNano: int64(time.Second) * int64(i+1),
34+
})
35+
}
36+
tests := []struct {
37+
name, format string
38+
}{
39+
{
40+
name: "default",
41+
},
42+
{
43+
name: "json",
44+
format: "json",
45+
},
46+
{
47+
name: "json template",
48+
format: "{{ json . }}",
49+
},
50+
{
51+
name: "json action",
52+
format: "{{ json .Action }}",
53+
},
54+
}
55+
56+
for _, tc := range tests {
57+
tc := tc
58+
t.Run(tc.name, func(t *testing.T) {
59+
// Set to UTC timezone as timestamps in output are
60+
// printed in the current timezone
61+
t.Setenv("TZ", "UTC")
62+
cli := test.NewFakeCli(&fakeClient{eventsFn: func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error) {
63+
messages := make(chan events.Message)
64+
errs := make(chan error, 1)
65+
go func() {
66+
for _, msg := range evts {
67+
messages <- msg
68+
}
69+
errs <- io.EOF
70+
}()
71+
return messages, errs
72+
}})
73+
cmd := NewEventsCommand(cli)
74+
if tc.format != "" {
75+
cmd.Flags().Set("format", tc.format)
76+
}
77+
assert.Check(t, cmd.Execute())
78+
out := cli.OutBuffer().String()
79+
assert.Check(t, golden.String(out, fmt.Sprintf("docker-events-%s.golden", strings.ReplaceAll(tc.name, " ", "-"))))
80+
cli.OutBuffer().Reset()
81+
})
82+
}
83+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
1970-01-01T00:00:01.000000000Z container create abc123 (image=ubuntu:latest)
2+
1970-01-01T00:00:02.000000000Z container start abc123 (image=ubuntu:latest)
3+
1970-01-01T00:00:03.000000000Z container attach abc123 (image=ubuntu:latest)
4+
1970-01-01T00:00:04.000000000Z container die abc123 (image=ubuntu:latest)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"create"
2+
"start"
3+
"attach"
4+
"die"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000}
2+
{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000}
3+
{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000}
4+
{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{"status":"create","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"create","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":1000000000,"timeNano":1000000000}
2+
{"status":"start","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"start","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":2000000000,"timeNano":2000000000}
3+
{"status":"attach","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"attach","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":3000000000,"timeNano":3000000000}
4+
{"status":"die","id":"abc123","from":"ubuntu:latest","Type":"container","Action":"die","Actor":{"ID":"abc123","Attributes":{"image":"ubuntu:latest"}},"scope":"local","time":4000000000,"timeNano":4000000000}

docs/reference/commandline/events.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Get real time events from the server
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:---------------------------------------|:---------|:--------|:----------------------------------------------|
14-
| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
15-
| [`--format`](#format) | `string` | | Format the output using the given Go template |
16-
| [`--since`](#since) | `string` | | Show all events created since timestamp |
17-
| `--until` | `string` | | Stream events until this timestamp |
12+
| Name | Type | Default | Description |
13+
|:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
14+
| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
15+
| [`--format`](#format) | `string` | | Format output using a custom template:<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
16+
| [`--since`](#since) | `string` | | Show all events created since timestamp |
17+
| `--until` | `string` | | Stream events until this timestamp |
1818

1919

2020
<!---MARKER_GEN_END-->
@@ -401,12 +401,17 @@ Type=container Status=destroy ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299
401401

402402
#### Format as JSON
403403

404+
To list events in JSON format, use the `json` directive, which is the equivalent
405+
of `--format '{{ json . }}`.
406+
404407
```console
405-
$ docker events --format '{{json .}}'
408+
$ docker events --format json
406409

407410
{"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
408411
{"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
409412
{"Type":"network","Action":"connect","Actor":{"ID":"1b50a5bf755f6021dfa78e..
410413
{"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
411414
{"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
412415
```
416+
417+
.

docs/reference/commandline/system_events.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ Get real time events from the server
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:---------------------------------------|:---------|:--------|:----------------------------------------------|
14-
| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
15-
| [`--format`](#format) | `string` | | Format the output using the given Go template |
16-
| `--since` | `string` | | Show all events created since timestamp |
17-
| `--until` | `string` | | Stream events until this timestamp |
12+
| Name | Type | Default | Description |
13+
|:---------------------------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
14+
| [`-f`](#filter), [`--filter`](#filter) | `filter` | | Filter output based on conditions provided |
15+
| [`--format`](#format) | `string` | | Format output using a custom template:<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
16+
| `--since` | `string` | | Show all events created since timestamp |
17+
| `--until` | `string` | | Stream events until this timestamp |
1818

1919

2020
<!---MARKER_GEN_END-->

0 commit comments

Comments
 (0)