Browse code

add `docker events --format`

Signed-off-by: Akihiro Suda <suda.akihiro@lab.ntt.co.jp>

Akihiro Suda authored on 2016/09/02 16:40:06
Showing 7 changed files
... ...
@@ -3,8 +3,10 @@ package system
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"io/ioutil"
6 7
 	"sort"
7 8
 	"strings"
9
+	"text/template"
8 10
 	"time"
9 11
 
10 12
 	"golang.org/x/net/context"
... ...
@@ -15,6 +17,7 @@ import (
15 15
 	"github.com/docker/docker/cli/command"
16 16
 	"github.com/docker/docker/opts"
17 17
 	"github.com/docker/docker/pkg/jsonlog"
18
+	"github.com/docker/docker/utils/templates"
18 19
 	"github.com/spf13/cobra"
19 20
 )
20 21
 
... ...
@@ -22,6 +25,7 @@ type eventsOptions struct {
22 22
 	since  string
23 23
 	until  string
24 24
 	filter opts.FilterOpt
25
+	format string
25 26
 }
26 27
 
27 28
 // NewEventsCommand creates a new cobra.Command for `docker events`
... ...
@@ -41,11 +45,18 @@ func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
41 41
 	flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
42 42
 	flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
43 43
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
44
+	flags.StringVar(&opts.format, "format", "", "Format the output using the given go template")
44 45
 
45 46
 	return cmd
46 47
 }
47 48
 
48 49
 func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
50
+	tmpl, err := makeTemplate(opts.format)
51
+	if err != nil {
52
+		return cli.StatusError{
53
+			StatusCode: 64,
54
+			Status:     "Error parsing format: " + err.Error()}
55
+	}
49 56
 	options := types.EventsOptions{
50 57
 		Since:   opts.since,
51 58
 		Until:   opts.until,
... ...
@@ -58,33 +69,48 @@ func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
58 58
 	}
59 59
 	defer responseBody.Close()
60 60
 
61
-	return streamEvents(responseBody, dockerCli.Out())
61
+	return streamEvents(dockerCli.Out(), responseBody, tmpl)
62
+}
63
+
64
+func makeTemplate(format string) (*template.Template, error) {
65
+	if format == "" {
66
+		return nil, nil
67
+	}
68
+	tmpl, err := templates.Parse(format)
69
+	if err != nil {
70
+		return tmpl, err
71
+	}
72
+	// we execute the template for an empty message, so as to validate
73
+	// a bad template like "{{.badFieldString}}"
74
+	return tmpl, tmpl.Execute(ioutil.Discard, &eventtypes.Message{})
62 75
 }
63 76
 
64 77
 // streamEvents decodes prints the incoming events in the provided output.
65
-func streamEvents(input io.Reader, output io.Writer) error {
78
+func streamEvents(out io.Writer, input io.Reader, tmpl *template.Template) error {
66 79
 	return DecodeEvents(input, func(event eventtypes.Message, err error) error {
67 80
 		if err != nil {
68 81
 			return err
69 82
 		}
70
-		printOutput(event, output)
71
-		return nil
83
+		if tmpl == nil {
84
+			return prettyPrintEvent(out, event)
85
+		}
86
+		return formatEvent(out, event, tmpl)
72 87
 	})
73 88
 }
74 89
 
75 90
 type eventProcessor func(event eventtypes.Message, err error) error
76 91
 
77
-// printOutput prints all types of event information.
92
+// prettyPrintEvent prints all types of event information.
78 93
 // Each output includes the event type, actor id, name and action.
79 94
 // Actor attributes are printed at the end if the actor has any.
80
-func printOutput(event eventtypes.Message, output io.Writer) {
95
+func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
81 96
 	if event.TimeNano != 0 {
82
-		fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
97
+		fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
83 98
 	} else if event.Time != 0 {
84
-		fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
99
+		fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
85 100
 	}
86 101
 
87
-	fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
102
+	fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)
88 103
 
89 104
 	if len(event.Actor.Attributes) > 0 {
90 105
 		var attrs []string
... ...
@@ -97,7 +123,13 @@ func printOutput(event eventtypes.Message, output io.Writer) {
97 97
 			v := event.Actor.Attributes[k]
98 98
 			attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
99 99
 		}
100
-		fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
100
+		fmt.Fprintf(out, " (%s)", strings.Join(attrs, ", "))
101 101
 	}
102
-	fmt.Fprint(output, "\n")
102
+	fmt.Fprint(out, "\n")
103
+	return nil
104
+}
105
+
106
+func formatEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
107
+	defer out.Write([]byte{'\n'})
108
+	return tmpl.Execute(out, event)
103 109
 }
... ...
@@ -1162,7 +1162,7 @@ _docker_events() {
1162 1162
 
1163 1163
 	case "$cur" in
1164 1164
 		-*)
1165
-			COMPREPLY=( $( compgen -W "--filter -f --help --since --until" -- "$cur" ) )
1165
+			COMPREPLY=( $( compgen -W "--filter -f --help --since --until --format" -- "$cur" ) )
1166 1166
 			;;
1167 1167
 	esac
1168 1168
 }
... ...
@@ -164,6 +164,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from events' -s f -l filter
164 164
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l help -d 'Print usage'
165 165
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l since -d 'Show all events created since timestamp'
166 166
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l until -d 'Stream events until this timestamp'
167
+complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l format -d 'Format the output using the given go template'
167 168
 
168 169
 # exec
169 170
 complete -c docker -f -n '__fish_docker_no_subcommand' -a exec -d 'Run a command in a running container'
... ...
@@ -1660,7 +1660,8 @@ __docker_subcommand() {
1660 1660
                 $opts_help \
1661 1661
                 "($help)*"{-f=,--filter=}"[Filter values]:filter:__docker_complete_events_filter" \
1662 1662
                 "($help)--since=[Events created since this timestamp]:timestamp: " \
1663
-                "($help)--until=[Events created until this timestamp]:timestamp: " && ret=0
1663
+                "($help)--until=[Events created until this timestamp]:timestamp: " \
1664
+                "($help)--format=[Format the output using the given go template]:template: " && ret=0
1664 1665
             ;;
1665 1666
         (exec)
1666 1667
             local state
... ...
@@ -17,6 +17,7 @@ Get real time events from the server
17 17
 
18 18
 Options:
19 19
   -f, --filter value   Filter output based on conditions provided (default [])
20
+      --format string  Format the output using the given go template
20 21
       --help           Print usage
21 22
       --since string   Show all events created since timestamp
22 23
       --until string   Stream events until this timestamp
... ...
@@ -85,6 +86,16 @@ The currently supported filters are:
85 85
 * network (`network=<name or id>`)
86 86
 * daemon (`daemon=<name or id>`)
87 87
 
88
+## Format
89
+
90
+If a format (`--format`) is specified, the given template will be executed
91
+instead of the default
92
+format. Go's [text/template](http://golang.org/pkg/text/template/) package
93
+describes all the details of the format.
94
+
95
+If a format is set to `{{json .}}`, the events are streamed as valid JSON
96
+Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
97
+
88 98
 ## Examples
89 99
 
90 100
 You'll need two shells for this example.
... ...
@@ -180,3 +191,22 @@ relative to the current time on the client machine:
180 180
     $ docker events --filter 'type=plugin' (experimental)
181 181
     2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
182 182
     2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
183
+
184
+**Format:**
185
+
186
+    $ docker events --filter 'type=container' --format 'Type={{.Type}}  Status={{.Status}}  ID={{.ID}}'
187
+    Type=container  Status=create  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
188
+    Type=container  Status=attach  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
189
+    Type=container  Status=start  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
190
+    Type=container  Status=resize  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
191
+    Type=container  Status=die  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
192
+    Type=container  Status=destroy  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
193
+
194
+**Format (as JSON Lines):**
195
+
196
+    $ docker events --format '{{json .}}'
197
+    {"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
198
+    {"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
199
+    {"Type":"network","Action":"connect","Actor":{"ID":"1b50a5bf755f6021dfa78e..
200
+    {"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
201
+    {"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
... ...
@@ -2,7 +2,9 @@ package main
2 2
 
3 3
 import (
4 4
 	"bufio"
5
+	"encoding/json"
5 6
 	"fmt"
7
+	"io"
6 8
 	"io/ioutil"
7 9
 	"net/http"
8 10
 	"os"
... ...
@@ -11,6 +13,7 @@ import (
11 11
 	"sync"
12 12
 	"time"
13 13
 
14
+	eventtypes "github.com/docker/docker/api/types/events"
14 15
 	eventstestutils "github.com/docker/docker/daemon/events/testutils"
15 16
 	"github.com/docker/docker/pkg/integration/checker"
16 17
 	icmd "github.com/docker/docker/pkg/integration/cmd"
... ...
@@ -745,3 +748,46 @@ func (s *DockerSuite) TestEventsUntilInThePast(c *check.C) {
745 745
 	c.Assert(out, checker.Not(checker.Contains), "test-container2")
746 746
 	c.Assert(out, checker.Contains, "test-container")
747 747
 }
748
+
749
+func (s *DockerSuite) TestEventsFormat(c *check.C) {
750
+	since := daemonUnixTime(c)
751
+	dockerCmd(c, "run", "--rm", "busybox", "true")
752
+	dockerCmd(c, "run", "--rm", "busybox", "true")
753
+	out, _ := dockerCmd(c, "events", "--since", since, "--until", daemonUnixTime(c), "--format", "{{json .}}")
754
+	dec := json.NewDecoder(strings.NewReader(out))
755
+	// make sure we got 2 start events
756
+	startCount := 0
757
+	for {
758
+		var err error
759
+		var ev eventtypes.Message
760
+		if err = dec.Decode(&ev); err == io.EOF {
761
+			break
762
+		}
763
+		c.Assert(err, checker.IsNil)
764
+		if ev.Status == "start" {
765
+			startCount++
766
+		}
767
+	}
768
+
769
+	c.Assert(startCount, checker.Equals, 2, check.Commentf("should have had 2 start events but had %d, out: %s", startCount, out))
770
+}
771
+
772
+func (s *DockerSuite) TestEventsFormatBadFunc(c *check.C) {
773
+	// make sure it fails immediately, without receiving any event
774
+	result := dockerCmdWithResult("events", "--format", "{{badFuncString .}}")
775
+	c.Assert(result, icmd.Matches, icmd.Expected{
776
+		Error:    "exit status 64",
777
+		ExitCode: 64,
778
+		Err:      "Error parsing format: template: :1: function \"badFuncString\" not defined",
779
+	})
780
+}
781
+
782
+func (s *DockerSuite) TestEventsFormatBadField(c *check.C) {
783
+	// make sure it fails immediately, without receiving any event
784
+	result := dockerCmdWithResult("events", "--format", "{{.badFieldString}}")
785
+	c.Assert(result, icmd.Matches, icmd.Expected{
786
+		Error:    "exit status 64",
787
+		ExitCode: 64,
788
+		Err:      "Error parsing format: template: :1:2: executing \"\" at <.badFieldString>: can't evaluate field badFieldString in type *events.Message",
789
+	})
790
+}
... ...
@@ -10,6 +10,7 @@ docker-events - Get real time events from the server
10 10
 [**-f**|**--filter**[=*[]*]]
11 11
 [**--since**[=*SINCE*]]
12 12
 [**--until**[=*UNTIL*]]
13
+[**--format**[=*FORMAT*]]
13 14
 
14 15
 
15 16
 # DESCRIPTION
... ...
@@ -45,6 +46,9 @@ Docker networks report the following events:
45 45
 **--until**=""
46 46
    Stream events until this timestamp
47 47
 
48
+**--format**=""
49
+   Format the output using the given go template
50
+
48 51
 The `--since` and `--until` parameters can be Unix timestamps, date formatted
49 52
 timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
50 53
 relative to the client machine's time. If you do not provide the `--since` option,
... ...
@@ -96,6 +100,31 @@ relative to the current time on the client machine:
96 96
 If you do not provide the --since option, the command returns only new and/or
97 97
 live events.
98 98
 
99
+## Format
100
+
101
+If a format (`--format`) is specified, the given template will be executed
102
+instead of the default format. Go's **text/template** package describes all the
103
+details of the format.
104
+
105
+    # docker events --filter 'type=container' --format 'Type={{.Type}}  Status={{.Status}}  ID={{.ID}}'
106
+    Type=container  Status=create  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
107
+    Type=container  Status=attach  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
108
+    Type=container  Status=start  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
109
+    Type=container  Status=resize  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
110
+    Type=container  Status=die  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
111
+    Type=container  Status=destroy  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
112
+
113
+If a format is set to `{{json .}}`, the events are streamed as valid JSON
114
+Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
115
+
116
+    # docker events --format '{{json .}}'
117
+    {"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
118
+    {"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
119
+    {"Type":"network","Action":"connect","Actor":{"ID":"1b50a5bf755f6021dfa78e..
120
+    {"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
121
+    {"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
122
+
123
+
99 124
 # HISTORY
100 125
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
101 126
 based on docker.com source material and internal work.