Forráskód Böngészése

Merge pull request #26268 from AkihiroSuda/eventsjsonl

add `docker events --format`
Sebastiaan van Stijn 8 éve
szülő
commit
bb6fe56e88

+ 43 - 11
cli/command/system/events.go

@@ -3,8 +3,10 @@ package system
 import (
 import (
 	"fmt"
 	"fmt"
 	"io"
 	"io"
+	"io/ioutil"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
+	"text/template"
 	"time"
 	"time"
 
 
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
@@ -15,6 +17,7 @@ import (
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/opts"
 	"github.com/docker/docker/pkg/jsonlog"
 	"github.com/docker/docker/pkg/jsonlog"
+	"github.com/docker/docker/utils/templates"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 )
 )
 
 
@@ -22,6 +25,7 @@ type eventsOptions struct {
 	since  string
 	since  string
 	until  string
 	until  string
 	filter opts.FilterOpt
 	filter opts.FilterOpt
+	format string
 }
 }
 
 
 // NewEventsCommand creates a new cobra.Command for `docker events`
 // NewEventsCommand creates a new cobra.Command for `docker events`
@@ -41,11 +45,18 @@ func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
 	flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
 	flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
 	flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
+	flags.StringVar(&opts.format, "format", "", "Format the output using the given go template")
 
 
 	return cmd
 	return cmd
 }
 }
 
 
 func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
 func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
+	tmpl, err := makeTemplate(opts.format)
+	if err != nil {
+		return cli.StatusError{
+			StatusCode: 64,
+			Status:     "Error parsing format: " + err.Error()}
+	}
 	options := types.EventsOptions{
 	options := types.EventsOptions{
 		Since:   opts.since,
 		Since:   opts.since,
 		Until:   opts.until,
 		Until:   opts.until,
@@ -58,33 +69,48 @@ func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
 	}
 	}
 	defer responseBody.Close()
 	defer responseBody.Close()
 
 
-	return streamEvents(responseBody, dockerCli.Out())
+	return streamEvents(dockerCli.Out(), responseBody, tmpl)
+}
+
+func makeTemplate(format string) (*template.Template, error) {
+	if format == "" {
+		return nil, nil
+	}
+	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(ioutil.Discard, &eventtypes.Message{})
 }
 }
 
 
 // streamEvents decodes prints the incoming events in the provided output.
 // streamEvents decodes prints the incoming events in the provided output.
-func streamEvents(input io.Reader, output io.Writer) error {
+func streamEvents(out io.Writer, input io.Reader, tmpl *template.Template) error {
 	return DecodeEvents(input, func(event eventtypes.Message, err error) error {
 	return DecodeEvents(input, func(event eventtypes.Message, err error) error {
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
-		printOutput(event, output)
-		return nil
+		if tmpl == nil {
+			return prettyPrintEvent(out, event)
+		}
+		return formatEvent(out, event, tmpl)
 	})
 	})
 }
 }
 
 
 type eventProcessor func(event eventtypes.Message, err error) error
 type eventProcessor func(event eventtypes.Message, err error) error
 
 
-// printOutput prints all types of event information.
+// prettyPrintEvent prints all types of event information.
 // Each output includes the event type, actor id, name and action.
 // Each output includes the event type, actor id, name and action.
 // Actor attributes are printed at the end if the actor has any.
 // Actor attributes are printed at the end if the actor has any.
-func printOutput(event eventtypes.Message, output io.Writer) {
+func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
 	if event.TimeNano != 0 {
 	if event.TimeNano != 0 {
-		fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
+		fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
 	} else if event.Time != 0 {
 	} else if event.Time != 0 {
-		fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
+		fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
 	}
 	}
 
 
-	fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
+	fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)
 
 
 	if len(event.Actor.Attributes) > 0 {
 	if len(event.Actor.Attributes) > 0 {
 		var attrs []string
 		var attrs []string
@@ -97,7 +123,13 @@ func printOutput(event eventtypes.Message, output io.Writer) {
 			v := event.Actor.Attributes[k]
 			v := event.Actor.Attributes[k]
 			attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
 			attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
 		}
 		}
-		fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
+		fmt.Fprintf(out, " (%s)", strings.Join(attrs, ", "))
 	}
 	}
-	fmt.Fprint(output, "\n")
+	fmt.Fprint(out, "\n")
+	return nil
+}
+
+func formatEvent(out io.Writer, event eventtypes.Message, tmpl *template.Template) error {
+	defer out.Write([]byte{'\n'})
+	return tmpl.Execute(out, event)
 }
 }

+ 1 - 1
contrib/completion/bash/docker

@@ -1162,7 +1162,7 @@ _docker_events() {
 
 
 	case "$cur" in
 	case "$cur" in
 		-*)
 		-*)
-			COMPREPLY=( $( compgen -W "--filter -f --help --since --until" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--filter -f --help --since --until --format" -- "$cur" ) )
 			;;
 			;;
 	esac
 	esac
 }
 }

+ 1 - 0
contrib/completion/fish/docker.fish

@@ -164,6 +164,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from events' -s f -l filter
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l help -d 'Print usage'
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l since -d 'Show all events created since timestamp'
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l since -d 'Show all events created since timestamp'
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l until -d 'Stream events until this timestamp'
 complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l until -d 'Stream events until this timestamp'
+complete -c docker -A -f -n '__fish_seen_subcommand_from events' -l format -d 'Format the output using the given go template'
 
 
 # exec
 # exec
 complete -c docker -f -n '__fish_docker_no_subcommand' -a exec -d 'Run a command in a running container'
 complete -c docker -f -n '__fish_docker_no_subcommand' -a exec -d 'Run a command in a running container'

+ 2 - 1
contrib/completion/zsh/_docker

@@ -1660,7 +1660,8 @@ __docker_subcommand() {
                 $opts_help \
                 $opts_help \
                 "($help)*"{-f=,--filter=}"[Filter values]:filter:__docker_complete_events_filter" \
                 "($help)*"{-f=,--filter=}"[Filter values]:filter:__docker_complete_events_filter" \
                 "($help)--since=[Events created since this timestamp]:timestamp: " \
                 "($help)--since=[Events created since this timestamp]:timestamp: " \
-                "($help)--until=[Events created until this timestamp]:timestamp: " && ret=0
+                "($help)--until=[Events created until this timestamp]:timestamp: " \
+                "($help)--format=[Format the output using the given go template]:template: " && ret=0
             ;;
             ;;
         (exec)
         (exec)
             local state
             local state

+ 30 - 0
docs/reference/commandline/events.md

@@ -17,6 +17,7 @@ Get real time events from the server
 
 
 Options:
 Options:
   -f, --filter value   Filter output based on conditions provided (default [])
   -f, --filter value   Filter output based on conditions provided (default [])
+      --format string  Format the output using the given go template
       --help           Print usage
       --help           Print usage
       --since string   Show all events created since timestamp
       --since string   Show all events created since timestamp
       --until string   Stream events until this timestamp
       --until string   Stream events until this timestamp
@@ -85,6 +86,16 @@ The currently supported filters are:
 * network (`network=<name or id>`)
 * network (`network=<name or id>`)
 * daemon (`daemon=<name or id>`)
 * daemon (`daemon=<name or id>`)
 
 
+## Format
+
+If a format (`--format`) is specified, the given template will be executed
+instead of the default
+format. Go's [text/template](http://golang.org/pkg/text/template/) package
+describes all the details of the format.
+
+If a format is set to `{{json .}}`, the events are streamed as valid JSON
+Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
+
 ## Examples
 ## Examples
 
 
 You'll need two shells for this example.
 You'll need two shells for this example.
@@ -180,3 +191,22 @@ relative to the current time on the client machine:
     $ docker events --filter 'type=plugin' (experimental)
     $ docker events --filter 'type=plugin' (experimental)
     2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
     2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
     2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
     2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
+
+**Format:**
+
+    $ docker events --filter 'type=container' --format 'Type={{.Type}}  Status={{.Status}}  ID={{.ID}}'
+    Type=container  Status=create  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=attach  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=start  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=resize  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=die  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=destroy  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+
+**Format (as JSON Lines):**
+
+    $ docker events --format '{{json .}}'
+    {"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
+    {"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
+    {"Type":"network","Action":"connect","Actor":{"ID":"1b50a5bf755f6021dfa78e..
+    {"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
+    {"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..

+ 46 - 0
integration-cli/docker_cli_events_test.go

@@ -2,7 +2,9 @@ package main
 
 
 import (
 import (
 	"bufio"
 	"bufio"
+	"encoding/json"
 	"fmt"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"io/ioutil"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
@@ -11,6 +13,7 @@ import (
 	"sync"
 	"sync"
 	"time"
 	"time"
 
 
+	eventtypes "github.com/docker/docker/api/types/events"
 	eventstestutils "github.com/docker/docker/daemon/events/testutils"
 	eventstestutils "github.com/docker/docker/daemon/events/testutils"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/integration/checker"
 	icmd "github.com/docker/docker/pkg/integration/cmd"
 	icmd "github.com/docker/docker/pkg/integration/cmd"
@@ -745,3 +748,46 @@ func (s *DockerSuite) TestEventsUntilInThePast(c *check.C) {
 	c.Assert(out, checker.Not(checker.Contains), "test-container2")
 	c.Assert(out, checker.Not(checker.Contains), "test-container2")
 	c.Assert(out, checker.Contains, "test-container")
 	c.Assert(out, checker.Contains, "test-container")
 }
 }
+
+func (s *DockerSuite) TestEventsFormat(c *check.C) {
+	since := daemonUnixTime(c)
+	dockerCmd(c, "run", "--rm", "busybox", "true")
+	dockerCmd(c, "run", "--rm", "busybox", "true")
+	out, _ := dockerCmd(c, "events", "--since", since, "--until", daemonUnixTime(c), "--format", "{{json .}}")
+	dec := json.NewDecoder(strings.NewReader(out))
+	// make sure we got 2 start events
+	startCount := 0
+	for {
+		var err error
+		var ev eventtypes.Message
+		if err = dec.Decode(&ev); err == io.EOF {
+			break
+		}
+		c.Assert(err, checker.IsNil)
+		if ev.Status == "start" {
+			startCount++
+		}
+	}
+
+	c.Assert(startCount, checker.Equals, 2, check.Commentf("should have had 2 start events but had %d, out: %s", startCount, out))
+}
+
+func (s *DockerSuite) TestEventsFormatBadFunc(c *check.C) {
+	// make sure it fails immediately, without receiving any event
+	result := dockerCmdWithResult("events", "--format", "{{badFuncString .}}")
+	c.Assert(result, icmd.Matches, icmd.Expected{
+		Error:    "exit status 64",
+		ExitCode: 64,
+		Err:      "Error parsing format: template: :1: function \"badFuncString\" not defined",
+	})
+}
+
+func (s *DockerSuite) TestEventsFormatBadField(c *check.C) {
+	// make sure it fails immediately, without receiving any event
+	result := dockerCmdWithResult("events", "--format", "{{.badFieldString}}")
+	c.Assert(result, icmd.Matches, icmd.Expected{
+		Error:    "exit status 64",
+		ExitCode: 64,
+		Err:      "Error parsing format: template: :1:2: executing \"\" at <.badFieldString>: can't evaluate field badFieldString in type *events.Message",
+	})
+}

+ 29 - 0
man/docker-events.1.md

@@ -10,6 +10,7 @@ docker-events - Get real time events from the server
 [**-f**|**--filter**[=*[]*]]
 [**-f**|**--filter**[=*[]*]]
 [**--since**[=*SINCE*]]
 [**--since**[=*SINCE*]]
 [**--until**[=*UNTIL*]]
 [**--until**[=*UNTIL*]]
+[**--format**[=*FORMAT*]]
 
 
 
 
 # DESCRIPTION
 # DESCRIPTION
@@ -45,6 +46,9 @@ Docker networks report the following events:
 **--until**=""
 **--until**=""
    Stream events until this timestamp
    Stream events until this timestamp
 
 
+**--format**=""
+   Format the output using the given go template
+
 The `--since` and `--until` parameters can be Unix timestamps, date formatted
 The `--since` and `--until` parameters can be Unix timestamps, date formatted
 timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
 timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed
 relative to the client machine's time. If you do not provide the `--since` option,
 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:
 If you do not provide the --since option, the command returns only new and/or
 If you do not provide the --since option, the command returns only new and/or
 live events.
 live events.
 
 
+## Format
+
+If a format (`--format`) is specified, the given template will be executed
+instead of the default format. Go's **text/template** package describes all the
+details of the format.
+
+    # docker events --filter 'type=container' --format 'Type={{.Type}}  Status={{.Status}}  ID={{.ID}}'
+    Type=container  Status=create  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=attach  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=start  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=resize  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=die  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+    Type=container  Status=destroy  ID=2ee349dac409e97974ce8d01b70d250b85e0ba8189299c126a87812311951e26
+
+If a format is set to `{{json .}}`, the events are streamed as valid JSON
+Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
+
+    # docker events --format '{{json .}}'
+    {"status":"create","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
+    {"status":"attach","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
+    {"Type":"network","Action":"connect","Actor":{"ID":"1b50a5bf755f6021dfa78e..
+    {"status":"start","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f42..
+    {"status":"resize","id":"196016a57679bf42424484918746a9474cd905dd993c4d0f4..
+
+
 # HISTORY
 # HISTORY
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
 April 2014, Originally compiled by William Henry (whenry at redhat dot com)
 based on docker.com source material and internal work.
 based on docker.com source material and internal work.