- Stop serializing JSONMessage in favor of events.Message.
- Keep backwards compatibility with JSONMessage for container events.
Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -1,11 +1,18 @@ |
| 1 | 1 |
package client |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "time" |
|
| 9 |
+ |
|
| 4 | 10 |
"github.com/docker/docker/api/types" |
| 11 |
+ eventtypes "github.com/docker/docker/api/types/events" |
|
| 5 | 12 |
"github.com/docker/docker/api/types/filters" |
| 6 | 13 |
Cli "github.com/docker/docker/cli" |
| 7 | 14 |
"github.com/docker/docker/opts" |
| 8 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 15 |
+ "github.com/docker/docker/pkg/jsonlog" |
|
| 9 | 16 |
flag "github.com/docker/docker/pkg/mflag" |
| 10 | 17 |
) |
| 11 | 18 |
|
| ... | ... |
@@ -46,5 +53,43 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
|
| 46 | 46 |
} |
| 47 | 47 |
defer responseBody.Close() |
| 48 | 48 |
|
| 49 |
- return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut) |
|
| 49 |
+ return streamEvents(responseBody, cli.out) |
|
| 50 |
+} |
|
| 51 |
+ |
|
| 52 |
+// streamEvents decodes prints the incoming events in the provided output. |
|
| 53 |
+func streamEvents(input io.Reader, output io.Writer) error {
|
|
| 54 |
+ dec := json.NewDecoder(input) |
|
| 55 |
+ for {
|
|
| 56 |
+ var event eventtypes.Message |
|
| 57 |
+ if err := dec.Decode(&event); err != nil {
|
|
| 58 |
+ if err == io.EOF {
|
|
| 59 |
+ break |
|
| 60 |
+ } |
|
| 61 |
+ return err |
|
| 62 |
+ } |
|
| 63 |
+ printOutput(event, output) |
|
| 64 |
+ } |
|
| 65 |
+ return nil |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+// printOutput prints all types of event information. |
|
| 69 |
+// Each output includes the event type, actor id, name and action. |
|
| 70 |
+// Actor attributes are printed at the end if the actor has any. |
|
| 71 |
+func printOutput(event eventtypes.Message, output io.Writer) {
|
|
| 72 |
+ if event.TimeNano != 0 {
|
|
| 73 |
+ fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed)) |
|
| 74 |
+ } else if event.Time != 0 {
|
|
| 75 |
+ fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed)) |
|
| 76 |
+ } |
|
| 77 |
+ |
|
| 78 |
+ fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID) |
|
| 79 |
+ |
|
| 80 |
+ if len(event.Actor.Attributes) > 0 {
|
|
| 81 |
+ var attrs []string |
|
| 82 |
+ for k, v := range event.Actor.Attributes {
|
|
| 83 |
+ attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
|
|
| 84 |
+ } |
|
| 85 |
+ fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", ")) |
|
| 86 |
+ } |
|
| 87 |
+ fmt.Fprint(output, "\n") |
|
| 50 | 88 |
} |
| ... | ... |
@@ -2,8 +2,8 @@ package system |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/docker/api/types" |
| 5 |
+ "github.com/docker/docker/api/types/events" |
|
| 5 | 6 |
"github.com/docker/docker/api/types/filters" |
| 6 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// Backend is the methods that need to be implemented to provide |
| ... | ... |
@@ -11,7 +11,7 @@ import ( |
| 11 | 11 |
type Backend interface {
|
| 12 | 12 |
SystemInfo() (*types.Info, error) |
| 13 | 13 |
SystemVersion() types.Version |
| 14 |
- SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]*jsonmessage.JSONMessage, chan interface{})
|
|
| 14 |
+ SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
|
|
| 15 | 15 |
UnsubscribeFromEvents(chan interface{})
|
| 16 | 16 |
AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error) |
| 17 | 17 |
} |
| ... | ... |
@@ -9,10 +9,10 @@ import ( |
| 9 | 9 |
"github.com/docker/docker/api" |
| 10 | 10 |
"github.com/docker/docker/api/server/httputils" |
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 |
+ "github.com/docker/docker/api/types/events" |
|
| 12 | 13 |
"github.com/docker/docker/api/types/filters" |
| 13 | 14 |
timetypes "github.com/docker/docker/api/types/time" |
| 14 | 15 |
"github.com/docker/docker/pkg/ioutils" |
| 15 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 16 | 16 |
"golang.org/x/net/context" |
| 17 | 17 |
) |
| 18 | 18 |
|
| ... | ... |
@@ -98,8 +98,9 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r * |
| 98 | 98 |
for {
|
| 99 | 99 |
select {
|
| 100 | 100 |
case ev := <-l: |
| 101 |
- jev, ok := ev.(*jsonmessage.JSONMessage) |
|
| 101 |
+ jev, ok := ev.(events.Message) |
|
| 102 | 102 |
if !ok {
|
| 103 |
+ logrus.Warnf("unexpected event message: %q", ev)
|
|
| 103 | 104 |
continue |
| 104 | 105 |
} |
| 105 | 106 |
if err := enc.Encode(jev); err != nil {
|
| 106 | 107 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+package events |
|
| 1 |
+ |
|
| 2 |
+const ( |
|
| 3 |
+ // ContainerEventType is the event type that containers generate |
|
| 4 |
+ ContainerEventType = "container" |
|
| 5 |
+ // ImageEventType is the event type that images generate |
|
| 6 |
+ ImageEventType = "image" |
|
| 7 |
+ // VolumeEventType is the event type that volumes generate |
|
| 8 |
+ VolumeEventType = "volume" |
|
| 9 |
+ // NetworkEventType is the event type that networks generate |
|
| 10 |
+ NetworkEventType = "network" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+// Actor describes something that generates events, |
|
| 14 |
+// like a container, or a network, or a volume. |
|
| 15 |
+// It has a defined name and a set or attributes. |
|
| 16 |
+// The container attributes are its labels, other actors |
|
| 17 |
+// can generate these attributes from other properties. |
|
| 18 |
+type Actor struct {
|
|
| 19 |
+ ID string |
|
| 20 |
+ Attributes map[string]string |
|
| 21 |
+} |
|
| 22 |
+ |
|
| 23 |
+// Message represents the information an event contains |
|
| 24 |
+type Message struct {
|
|
| 25 |
+ // Deprecated information from JSONMessage. |
|
| 26 |
+ // With data only in container events. |
|
| 27 |
+ Status string `json:"status,omitempty"` |
|
| 28 |
+ ID string `json:"id,omitempty"` |
|
| 29 |
+ From string `json:"from,omitempty"` |
|
| 30 |
+ |
|
| 31 |
+ Type string |
|
| 32 |
+ Action string |
|
| 33 |
+ Actor Actor |
|
| 34 |
+ |
|
| 35 |
+ Time int64 `json:"time,omitempty"` |
|
| 36 |
+ TimeNano int64 `json:"timeNano,omitempty"` |
|
| 37 |
+} |
| ... | ... |
@@ -197,6 +197,22 @@ func (filters Args) ExactMatch(field, source string) bool {
|
| 197 | 197 |
return false |
| 198 | 198 |
} |
| 199 | 199 |
|
| 200 |
+// FuzzyMatch returns true if the source matches exactly one of the filters, |
|
| 201 |
+// or the source has one of the filters as a prefix. |
|
| 202 |
+func (filters Args) FuzzyMatch(field, source string) bool {
|
|
| 203 |
+ if filters.ExactMatch(field, source) {
|
|
| 204 |
+ return true |
|
| 205 |
+ } |
|
| 206 |
+ |
|
| 207 |
+ fieldValues := filters.fields[field] |
|
| 208 |
+ for prefix := range fieldValues {
|
|
| 209 |
+ if strings.HasPrefix(source, prefix) {
|
|
| 210 |
+ return true |
|
| 211 |
+ } |
|
| 212 |
+ } |
|
| 213 |
+ return false |
|
| 214 |
+} |
|
| 215 |
+ |
|
| 200 | 216 |
// Include returns true if the name of the field to filter is in the filters. |
| 201 | 217 |
func (filters Args) Include(field string) bool {
|
| 202 | 218 |
_, ok := filters.fields[field] |
| ... | ... |
@@ -349,3 +349,21 @@ func TestWalkValues(t *testing.T) {
|
| 349 | 349 |
t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
|
| 350 | 350 |
} |
| 351 | 351 |
} |
| 352 |
+ |
|
| 353 |
+func TestFuzzyMatch(t *testing.T) {
|
|
| 354 |
+ f := NewArgs() |
|
| 355 |
+ f.Add("container", "foo")
|
|
| 356 |
+ |
|
| 357 |
+ cases := map[string]bool{
|
|
| 358 |
+ "foo": true, |
|
| 359 |
+ "foobar": true, |
|
| 360 |
+ "barfoo": false, |
|
| 361 |
+ "bar": false, |
|
| 362 |
+ } |
|
| 363 |
+ for source, match := range cases {
|
|
| 364 |
+ got := f.FuzzyMatch("container", source)
|
|
| 365 |
+ if got != match {
|
|
| 366 |
+ t.Fatalf("Expected %v, got %v: %s", match, got, source)
|
|
| 367 |
+ } |
|
| 368 |
+ } |
|
| 369 |
+} |
| ... | ... |
@@ -22,6 +22,7 @@ import ( |
| 22 | 22 |
"github.com/docker/docker/api" |
| 23 | 23 |
"github.com/docker/docker/api/types" |
| 24 | 24 |
containertypes "github.com/docker/docker/api/types/container" |
| 25 |
+ eventtypes "github.com/docker/docker/api/types/events" |
|
| 25 | 26 |
"github.com/docker/docker/api/types/filters" |
| 26 | 27 |
registrytypes "github.com/docker/docker/api/types/registry" |
| 27 | 28 |
"github.com/docker/docker/api/types/strslice" |
| ... | ... |
@@ -47,7 +48,6 @@ import ( |
| 47 | 47 |
"github.com/docker/docker/pkg/fileutils" |
| 48 | 48 |
"github.com/docker/docker/pkg/graphdb" |
| 49 | 49 |
"github.com/docker/docker/pkg/idtools" |
| 50 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 51 | 50 |
"github.com/docker/docker/pkg/mount" |
| 52 | 51 |
"github.com/docker/docker/pkg/namesgenerator" |
| 53 | 52 |
"github.com/docker/docker/pkg/progress" |
| ... | ... |
@@ -554,23 +554,9 @@ func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
|
| 554 | 554 |
return e, nil |
| 555 | 555 |
} |
| 556 | 556 |
|
| 557 |
-// getEventFilter returns a filters.Filter for a set of filters |
|
| 558 |
-func (daemon *Daemon) getEventFilter(filter filters.Args) *events.Filter {
|
|
| 559 |
- // incoming container filter can be name, id or partial id, convert to |
|
| 560 |
- // a full container id |
|
| 561 |
- for _, cn := range filter.Get("container") {
|
|
| 562 |
- c, err := daemon.GetContainer(cn) |
|
| 563 |
- filter.Del("container", cn)
|
|
| 564 |
- if err == nil {
|
|
| 565 |
- filter.Add("container", c.ID)
|
|
| 566 |
- } |
|
| 567 |
- } |
|
| 568 |
- return events.NewFilter(filter, daemon.GetLabels) |
|
| 569 |
-} |
|
| 570 |
- |
|
| 571 | 557 |
// SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events. |
| 572 |
-func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]*jsonmessage.JSONMessage, chan interface{}) {
|
|
| 573 |
- ef := daemon.getEventFilter(filter) |
|
| 558 |
+func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
|
|
| 559 |
+ ef := events.NewFilter(filter) |
|
| 574 | 560 |
return daemon.EventsService.SubscribeTopic(since, sinceNano, ef) |
| 575 | 561 |
} |
| 576 | 562 |
|
| ... | ... |
@@ -580,21 +566,6 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
|
| 580 | 580 |
daemon.EventsService.Evict(listener) |
| 581 | 581 |
} |
| 582 | 582 |
|
| 583 |
-// GetLabels for a container or image id |
|
| 584 |
-func (daemon *Daemon) GetLabels(id string) map[string]string {
|
|
| 585 |
- // TODO: TestCase |
|
| 586 |
- container := daemon.containers.Get(id) |
|
| 587 |
- if container != nil {
|
|
| 588 |
- return container.Config.Labels |
|
| 589 |
- } |
|
| 590 |
- |
|
| 591 |
- img, err := daemon.GetImage(id) |
|
| 592 |
- if err == nil {
|
|
| 593 |
- return img.ContainerConfig.Labels |
|
| 594 |
- } |
|
| 595 |
- return nil |
|
| 596 |
-} |
|
| 597 |
- |
|
| 598 | 583 |
// children returns all child containers of the container with the |
| 599 | 584 |
// given name. The containers are returned as a map from the container |
| 600 | 585 |
// name to a pointer to Container. |
| ... | ... |
@@ -1032,7 +1003,8 @@ func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error {
|
| 1032 | 1032 |
if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil {
|
| 1033 | 1033 |
return err |
| 1034 | 1034 |
} |
| 1035 |
- daemon.EventsService.Log("tag", newTag.String(), "")
|
|
| 1035 |
+ |
|
| 1036 |
+ daemon.LogImageEvent(imageID.String(), newTag.String(), "tag") |
|
| 1036 | 1037 |
return nil |
| 1037 | 1038 |
} |
| 1038 | 1039 |
|
| ... | ... |
@@ -1068,15 +1040,15 @@ func (daemon *Daemon) PullImage(ref reference.Named, metaHeaders map[string][]st |
| 1068 | 1068 |
}() |
| 1069 | 1069 |
|
| 1070 | 1070 |
imagePullConfig := &distribution.ImagePullConfig{
|
| 1071 |
- MetaHeaders: metaHeaders, |
|
| 1072 |
- AuthConfig: authConfig, |
|
| 1073 |
- ProgressOutput: progress.ChanOutput(progressChan), |
|
| 1074 |
- RegistryService: daemon.RegistryService, |
|
| 1075 |
- EventsService: daemon.EventsService, |
|
| 1076 |
- MetadataStore: daemon.distributionMetadataStore, |
|
| 1077 |
- ImageStore: daemon.imageStore, |
|
| 1078 |
- ReferenceStore: daemon.referenceStore, |
|
| 1079 |
- DownloadManager: daemon.downloadManager, |
|
| 1071 |
+ MetaHeaders: metaHeaders, |
|
| 1072 |
+ AuthConfig: authConfig, |
|
| 1073 |
+ ProgressOutput: progress.ChanOutput(progressChan), |
|
| 1074 |
+ RegistryService: daemon.RegistryService, |
|
| 1075 |
+ ImageEventLogger: daemon.LogImageEvent, |
|
| 1076 |
+ MetadataStore: daemon.distributionMetadataStore, |
|
| 1077 |
+ ImageStore: daemon.imageStore, |
|
| 1078 |
+ ReferenceStore: daemon.referenceStore, |
|
| 1079 |
+ DownloadManager: daemon.downloadManager, |
|
| 1080 | 1080 |
} |
| 1081 | 1081 |
|
| 1082 | 1082 |
err := distribution.Pull(ctx, ref, imagePullConfig) |
| ... | ... |
@@ -1111,17 +1083,17 @@ func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]st |
| 1111 | 1111 |
}() |
| 1112 | 1112 |
|
| 1113 | 1113 |
imagePushConfig := &distribution.ImagePushConfig{
|
| 1114 |
- MetaHeaders: metaHeaders, |
|
| 1115 |
- AuthConfig: authConfig, |
|
| 1116 |
- ProgressOutput: progress.ChanOutput(progressChan), |
|
| 1117 |
- RegistryService: daemon.RegistryService, |
|
| 1118 |
- EventsService: daemon.EventsService, |
|
| 1119 |
- MetadataStore: daemon.distributionMetadataStore, |
|
| 1120 |
- LayerStore: daemon.layerStore, |
|
| 1121 |
- ImageStore: daemon.imageStore, |
|
| 1122 |
- ReferenceStore: daemon.referenceStore, |
|
| 1123 |
- TrustKey: daemon.trustKey, |
|
| 1124 |
- UploadManager: daemon.uploadManager, |
|
| 1114 |
+ MetaHeaders: metaHeaders, |
|
| 1115 |
+ AuthConfig: authConfig, |
|
| 1116 |
+ ProgressOutput: progress.ChanOutput(progressChan), |
|
| 1117 |
+ RegistryService: daemon.RegistryService, |
|
| 1118 |
+ ImageEventLogger: daemon.LogImageEvent, |
|
| 1119 |
+ MetadataStore: daemon.distributionMetadataStore, |
|
| 1120 |
+ LayerStore: daemon.layerStore, |
|
| 1121 |
+ ImageStore: daemon.imageStore, |
|
| 1122 |
+ ReferenceStore: daemon.referenceStore, |
|
| 1123 |
+ TrustKey: daemon.trustKey, |
|
| 1124 |
+ UploadManager: daemon.uploadManager, |
|
| 1125 | 1125 |
} |
| 1126 | 1126 |
|
| 1127 | 1127 |
err := distribution.Push(ctx, ref, imagePushConfig) |
| ... | ... |
@@ -1,14 +1,82 @@ |
| 1 | 1 |
package daemon |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/api/types/events" |
|
| 4 | 7 |
"github.com/docker/docker/container" |
| 8 |
+ "github.com/docker/libnetwork" |
|
| 5 | 9 |
) |
| 6 | 10 |
|
| 7 | 11 |
// LogContainerEvent generates an event related to a container. |
| 8 | 12 |
func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
|
| 9 |
- daemon.EventsService.Log( |
|
| 10 |
- action, |
|
| 11 |
- container.ID, |
|
| 12 |
- container.Config.Image, |
|
| 13 |
- ) |
|
| 13 |
+ attributes := copyAttributes(container.Config.Labels) |
|
| 14 |
+ if container.Config.Image != "" {
|
|
| 15 |
+ attributes["image"] = container.Config.Image |
|
| 16 |
+ } |
|
| 17 |
+ attributes["name"] = strings.TrimLeft(container.Name, "/") |
|
| 18 |
+ |
|
| 19 |
+ actor := events.Actor{
|
|
| 20 |
+ ID: container.ID, |
|
| 21 |
+ Attributes: attributes, |
|
| 22 |
+ } |
|
| 23 |
+ daemon.EventsService.Log(action, events.ContainerEventType, actor) |
|
| 24 |
+} |
|
| 25 |
+ |
|
| 26 |
+// LogImageEvent generates an event related to a container. |
|
| 27 |
+func (daemon *Daemon) LogImageEvent(imageID, refName, action string) {
|
|
| 28 |
+ attributes := map[string]string{}
|
|
| 29 |
+ img, err := daemon.GetImage(imageID) |
|
| 30 |
+ if err == nil && img.Config != nil {
|
|
| 31 |
+ // image has not been removed yet. |
|
| 32 |
+ // it could be missing if the event is `delete`. |
|
| 33 |
+ attributes = copyAttributes(img.Config.Labels) |
|
| 34 |
+ } |
|
| 35 |
+ if refName != "" {
|
|
| 36 |
+ attributes["name"] = refName |
|
| 37 |
+ } |
|
| 38 |
+ actor := events.Actor{
|
|
| 39 |
+ ID: imageID, |
|
| 40 |
+ Attributes: attributes, |
|
| 41 |
+ } |
|
| 42 |
+ |
|
| 43 |
+ daemon.EventsService.Log(action, events.ImageEventType, actor) |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+// LogVolumeEvent generates an event related to a volume. |
|
| 47 |
+func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
|
|
| 48 |
+ actor := events.Actor{
|
|
| 49 |
+ ID: volumeID, |
|
| 50 |
+ Attributes: attributes, |
|
| 51 |
+ } |
|
| 52 |
+ daemon.EventsService.Log(action, events.VolumeEventType, actor) |
|
| 53 |
+} |
|
| 54 |
+ |
|
| 55 |
+// LogNetworkEvent generates an event related to a network with only the default attributes. |
|
| 56 |
+func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
|
|
| 57 |
+ daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
|
|
| 58 |
+} |
|
| 59 |
+ |
|
| 60 |
+// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes. |
|
| 61 |
+func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
|
|
| 62 |
+ attributes["name"] = nw.Name() |
|
| 63 |
+ attributes["type"] = nw.Type() |
|
| 64 |
+ |
|
| 65 |
+ actor := events.Actor{
|
|
| 66 |
+ ID: nw.ID(), |
|
| 67 |
+ Attributes: attributes, |
|
| 68 |
+ } |
|
| 69 |
+ daemon.EventsService.Log(action, events.NetworkEventType, actor) |
|
| 70 |
+} |
|
| 71 |
+ |
|
| 72 |
+// copyAttributes guarantees that labels are not mutated by event triggers. |
|
| 73 |
+func copyAttributes(labels map[string]string) map[string]string {
|
|
| 74 |
+ attributes := map[string]string{}
|
|
| 75 |
+ if labels == nil {
|
|
| 76 |
+ return attributes |
|
| 77 |
+ } |
|
| 78 |
+ for k, v := range labels {
|
|
| 79 |
+ attributes[k] = v |
|
| 80 |
+ } |
|
| 81 |
+ return attributes |
|
| 14 | 82 |
} |
| ... | ... |
@@ -4,7 +4,7 @@ import ( |
| 4 | 4 |
"sync" |
| 5 | 5 |
"time" |
| 6 | 6 |
|
| 7 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 7 |
+ eventtypes "github.com/docker/docker/api/types/events" |
|
| 8 | 8 |
"github.com/docker/docker/pkg/pubsub" |
| 9 | 9 |
) |
| 10 | 10 |
|
| ... | ... |
@@ -13,17 +13,17 @@ const ( |
| 13 | 13 |
bufferSize = 1024 |
| 14 | 14 |
) |
| 15 | 15 |
|
| 16 |
-// Events is pubsub channel for *jsonmessage.JSONMessage |
|
| 16 |
+// Events is pubsub channel for events generated by the engine. |
|
| 17 | 17 |
type Events struct {
|
| 18 | 18 |
mu sync.Mutex |
| 19 |
- events []*jsonmessage.JSONMessage |
|
| 19 |
+ events []eventtypes.Message |
|
| 20 | 20 |
pub *pubsub.Publisher |
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 | 23 |
// New returns new *Events instance |
| 24 | 24 |
func New() *Events {
|
| 25 | 25 |
return &Events{
|
| 26 |
- events: make([]*jsonmessage.JSONMessage, 0, eventsLimit), |
|
| 26 |
+ events: make([]eventtypes.Message, 0, eventsLimit), |
|
| 27 | 27 |
pub: pubsub.NewPublisher(100*time.Millisecond, bufferSize), |
| 28 | 28 |
} |
| 29 | 29 |
} |
| ... | ... |
@@ -32,9 +32,9 @@ func New() *Events {
|
| 32 | 32 |
// last events, a channel in which you can expect new events (in form |
| 33 | 33 |
// of interface{}, so you need type assertion), and a function to call
|
| 34 | 34 |
// to stop the stream of events. |
| 35 |
-func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func()) {
|
|
| 35 |
+func (e *Events) Subscribe() ([]eventtypes.Message, chan interface{}, func()) {
|
|
| 36 | 36 |
e.mu.Lock() |
| 37 |
- current := make([]*jsonmessage.JSONMessage, len(e.events)) |
|
| 37 |
+ current := make([]eventtypes.Message, len(e.events)) |
|
| 38 | 38 |
copy(current, e.events) |
| 39 | 39 |
l := e.pub.Subscribe() |
| 40 | 40 |
e.mu.Unlock() |
| ... | ... |
@@ -48,13 +48,13 @@ func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func
|
| 48 | 48 |
// SubscribeTopic adds new listener to events, returns slice of 64 stored |
| 49 | 49 |
// last events, a channel in which you can expect new events (in form |
| 50 | 50 |
// of interface{}, so you need type assertion).
|
| 51 |
-func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmessage.JSONMessage, chan interface{}) {
|
|
| 51 |
+func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]eventtypes.Message, chan interface{}) {
|
|
| 52 | 52 |
e.mu.Lock() |
| 53 | 53 |
defer e.mu.Unlock() |
| 54 | 54 |
|
| 55 |
- var buffered []*jsonmessage.JSONMessage |
|
| 55 |
+ var buffered []eventtypes.Message |
|
| 56 | 56 |
topic := func(m interface{}) bool {
|
| 57 |
- return ef.Include(m.(*jsonmessage.JSONMessage)) |
|
| 57 |
+ return ef.Include(m.(eventtypes.Message)) |
|
| 58 | 58 |
} |
| 59 | 59 |
|
| 60 | 60 |
if since != -1 {
|
| ... | ... |
@@ -64,7 +64,7 @@ func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmess |
| 64 | 64 |
break |
| 65 | 65 |
} |
| 66 | 66 |
if ef.filter.Len() == 0 || topic(ev) {
|
| 67 |
- buffered = append([]*jsonmessage.JSONMessage{ev}, buffered...)
|
|
| 67 |
+ buffered = append([]eventtypes.Message{ev}, buffered...)
|
|
| 68 | 68 |
} |
| 69 | 69 |
} |
| 70 | 70 |
} |
| ... | ... |
@@ -87,9 +87,27 @@ func (e *Events) Evict(l chan interface{}) {
|
| 87 | 87 |
|
| 88 | 88 |
// Log broadcasts event to listeners. Each listener has 100 millisecond for |
| 89 | 89 |
// receiving event or it will be skipped. |
| 90 |
-func (e *Events) Log(action, id, from string) {
|
|
| 90 |
+func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
|
|
| 91 | 91 |
now := time.Now().UTC() |
| 92 |
- jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now.Unix(), TimeNano: now.UnixNano()}
|
|
| 92 |
+ jm := eventtypes.Message{
|
|
| 93 |
+ Action: action, |
|
| 94 |
+ Type: eventType, |
|
| 95 |
+ Actor: actor, |
|
| 96 |
+ Time: now.Unix(), |
|
| 97 |
+ TimeNano: now.UnixNano(), |
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ // fill deprecated fields for container and images |
|
| 101 |
+ switch eventType {
|
|
| 102 |
+ case eventtypes.ContainerEventType: |
|
| 103 |
+ jm.ID = actor.ID |
|
| 104 |
+ jm.Status = action |
|
| 105 |
+ jm.From = actor.Attributes["image"] |
|
| 106 |
+ case eventtypes.ImageEventType: |
|
| 107 |
+ jm.ID = actor.ID |
|
| 108 |
+ jm.Status = action |
|
| 109 |
+ } |
|
| 110 |
+ |
|
| 93 | 111 |
e.mu.Lock() |
| 94 | 112 |
if len(e.events) == cap(e.events) {
|
| 95 | 113 |
// discard oldest event |
| ... | ... |
@@ -5,7 +5,7 @@ import ( |
| 5 | 5 |
"testing" |
| 6 | 6 |
"time" |
| 7 | 7 |
|
| 8 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 8 |
+ "github.com/docker/docker/api/types/events" |
|
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 | 11 |
func TestEventsLog(t *testing.T) {
|
| ... | ... |
@@ -18,10 +18,14 @@ func TestEventsLog(t *testing.T) {
|
| 18 | 18 |
if count != 2 {
|
| 19 | 19 |
t.Fatalf("Must be 2 subscribers, got %d", count)
|
| 20 | 20 |
} |
| 21 |
- e.Log("test", "cont", "image")
|
|
| 21 |
+ actor := events.Actor{
|
|
| 22 |
+ ID: "cont", |
|
| 23 |
+ Attributes: map[string]string{"image": "image"},
|
|
| 24 |
+ } |
|
| 25 |
+ e.Log("test", events.ContainerEventType, actor)
|
|
| 22 | 26 |
select {
|
| 23 | 27 |
case msg := <-l1: |
| 24 |
- jmsg, ok := msg.(*jsonmessage.JSONMessage) |
|
| 28 |
+ jmsg, ok := msg.(events.Message) |
|
| 25 | 29 |
if !ok {
|
| 26 | 30 |
t.Fatalf("Unexpected type %T", msg)
|
| 27 | 31 |
} |
| ... | ... |
@@ -42,7 +46,7 @@ func TestEventsLog(t *testing.T) {
|
| 42 | 42 |
} |
| 43 | 43 |
select {
|
| 44 | 44 |
case msg := <-l2: |
| 45 |
- jmsg, ok := msg.(*jsonmessage.JSONMessage) |
|
| 45 |
+ jmsg, ok := msg.(events.Message) |
|
| 46 | 46 |
if !ok {
|
| 47 | 47 |
t.Fatalf("Unexpected type %T", msg)
|
| 48 | 48 |
} |
| ... | ... |
@@ -70,7 +74,10 @@ func TestEventsLogTimeout(t *testing.T) {
|
| 70 | 70 |
|
| 71 | 71 |
c := make(chan struct{})
|
| 72 | 72 |
go func() {
|
| 73 |
- e.Log("test", "cont", "image")
|
|
| 73 |
+ actor := events.Actor{
|
|
| 74 |
+ ID: "image", |
|
| 75 |
+ } |
|
| 76 |
+ e.Log("test", events.ImageEventType, actor)
|
|
| 74 | 77 |
close(c) |
| 75 | 78 |
}() |
| 76 | 79 |
|
| ... | ... |
@@ -88,7 +95,12 @@ func TestLogEvents(t *testing.T) {
|
| 88 | 88 |
action := fmt.Sprintf("action_%d", i)
|
| 89 | 89 |
id := fmt.Sprintf("cont_%d", i)
|
| 90 | 90 |
from := fmt.Sprintf("image_%d", i)
|
| 91 |
- e.Log(action, id, from) |
|
| 91 |
+ |
|
| 92 |
+ actor := events.Actor{
|
|
| 93 |
+ ID: id, |
|
| 94 |
+ Attributes: map[string]string{"image": from},
|
|
| 95 |
+ } |
|
| 96 |
+ e.Log(action, events.ContainerEventType, actor) |
|
| 92 | 97 |
} |
| 93 | 98 |
time.Sleep(50 * time.Millisecond) |
| 94 | 99 |
current, l, _ := e.Subscribe() |
| ... | ... |
@@ -97,16 +109,21 @@ func TestLogEvents(t *testing.T) {
|
| 97 | 97 |
action := fmt.Sprintf("action_%d", num)
|
| 98 | 98 |
id := fmt.Sprintf("cont_%d", num)
|
| 99 | 99 |
from := fmt.Sprintf("image_%d", num)
|
| 100 |
- e.Log(action, id, from) |
|
| 100 |
+ |
|
| 101 |
+ actor := events.Actor{
|
|
| 102 |
+ ID: id, |
|
| 103 |
+ Attributes: map[string]string{"image": from},
|
|
| 104 |
+ } |
|
| 105 |
+ e.Log(action, events.ContainerEventType, actor) |
|
| 101 | 106 |
} |
| 102 | 107 |
if len(e.events) != eventsLimit {
|
| 103 | 108 |
t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
|
| 104 | 109 |
} |
| 105 | 110 |
|
| 106 |
- var msgs []*jsonmessage.JSONMessage |
|
| 111 |
+ var msgs []events.Message |
|
| 107 | 112 |
for len(msgs) < 10 {
|
| 108 | 113 |
m := <-l |
| 109 |
- jm, ok := (m).(*jsonmessage.JSONMessage) |
|
| 114 |
+ jm, ok := (m).(events.Message) |
|
| 110 | 115 |
if !ok {
|
| 111 | 116 |
t.Fatalf("Unexpected type %T", m)
|
| 112 | 117 |
} |
| ... | ... |
@@ -1,46 +1,58 @@ |
| 1 | 1 |
package events |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "github.com/docker/docker/api/types/events" |
|
| 4 | 5 |
"github.com/docker/docker/api/types/filters" |
| 5 |
- "github.com/docker/docker/pkg/jsonmessage" |
|
| 6 | 6 |
"github.com/docker/docker/reference" |
| 7 | 7 |
) |
| 8 | 8 |
|
| 9 | 9 |
// Filter can filter out docker events from a stream |
| 10 | 10 |
type Filter struct {
|
| 11 |
- filter filters.Args |
|
| 12 |
- getLabels func(id string) map[string]string |
|
| 11 |
+ filter filters.Args |
|
| 13 | 12 |
} |
| 14 | 13 |
|
| 15 | 14 |
// NewFilter creates a new Filter |
| 16 |
-func NewFilter(filter filters.Args, getLabels func(id string) map[string]string) *Filter {
|
|
| 17 |
- return &Filter{filter: filter, getLabels: getLabels}
|
|
| 15 |
+func NewFilter(filter filters.Args) *Filter {
|
|
| 16 |
+ return &Filter{filter: filter}
|
|
| 18 | 17 |
} |
| 19 | 18 |
|
| 20 | 19 |
// Include returns true when the event ev is included by the filters |
| 21 |
-func (ef *Filter) Include(ev *jsonmessage.JSONMessage) bool {
|
|
| 22 |
- return ef.filter.ExactMatch("event", ev.Status) &&
|
|
| 23 |
- ef.filter.ExactMatch("container", ev.ID) &&
|
|
| 24 |
- ef.isImageIncluded(ev.ID, ev.From) && |
|
| 25 |
- ef.isLabelFieldIncluded(ev.ID) |
|
| 20 |
+func (ef *Filter) Include(ev events.Message) bool {
|
|
| 21 |
+ if ev.Type != events.ContainerEventType && ev.Type != events.ImageEventType {
|
|
| 22 |
+ return false |
|
| 23 |
+ } |
|
| 24 |
+ return ef.filter.ExactMatch("event", ev.Action) &&
|
|
| 25 |
+ ef.matchContainer(ev) && |
|
| 26 |
+ ef.isImageIncluded(ev) && |
|
| 27 |
+ ef.isLabelFieldIncluded(ev.Actor.Attributes) |
|
| 26 | 28 |
} |
| 27 | 29 |
|
| 28 |
-func (ef *Filter) isLabelFieldIncluded(id string) bool {
|
|
| 30 |
+func (ef *Filter) isLabelFieldIncluded(attributes map[string]string) bool {
|
|
| 29 | 31 |
if !ef.filter.Include("label") {
|
| 30 | 32 |
return true |
| 31 | 33 |
} |
| 32 |
- return ef.filter.MatchKVList("label", ef.getLabels(id))
|
|
| 34 |
+ return ef.filter.MatchKVList("label", attributes)
|
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+func (ef *Filter) matchContainer(ev events.Message) bool {
|
|
| 38 |
+ return ef.filter.FuzzyMatch("container", ev.Actor.ID) ||
|
|
| 39 |
+ ef.filter.FuzzyMatch("container", ev.Actor.Attributes["name"])
|
|
| 33 | 40 |
} |
| 34 | 41 |
|
| 35 | 42 |
// The image filter will be matched against both event.ID (for image events) |
| 36 | 43 |
// and event.From (for container events), so that any container that was created |
| 37 | 44 |
// from an image will be included in the image events. Also compare both |
| 38 | 45 |
// against the stripped repo name without any tags. |
| 39 |
-func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
|
|
| 40 |
- return ef.filter.ExactMatch("image", eventID) ||
|
|
| 41 |
- ef.filter.ExactMatch("image", eventFrom) ||
|
|
| 42 |
- ef.filter.ExactMatch("image", stripTag(eventID)) ||
|
|
| 43 |
- ef.filter.ExactMatch("image", stripTag(eventFrom))
|
|
| 46 |
+func (ef *Filter) isImageIncluded(ev events.Message) bool {
|
|
| 47 |
+ id := ev.ID |
|
| 48 |
+ var imageName string |
|
| 49 |
+ if n, ok := ev.Actor.Attributes["image"]; ok {
|
|
| 50 |
+ imageName = n |
|
| 51 |
+ } |
|
| 52 |
+ return ef.filter.ExactMatch("image", id) ||
|
|
| 53 |
+ ef.filter.ExactMatch("image", imageName) ||
|
|
| 54 |
+ ef.filter.ExactMatch("image", stripTag(id)) ||
|
|
| 55 |
+ ef.filter.ExactMatch("image", stripTag(imageName))
|
|
| 44 | 56 |
} |
| 45 | 57 |
|
| 46 | 58 |
func stripTag(image string) string {
|
| 47 | 59 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "testing" |
|
| 4 |
+ |
|
| 5 |
+ containertypes "github.com/docker/docker/api/types/container" |
|
| 6 |
+ "github.com/docker/docker/container" |
|
| 7 |
+ "github.com/docker/docker/daemon/events" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+func TestLogContainerCopyLabels(t *testing.T) {
|
|
| 11 |
+ e := events.New() |
|
| 12 |
+ _, l, _ := e.Subscribe() |
|
| 13 |
+ defer e.Evict(l) |
|
| 14 |
+ |
|
| 15 |
+ container := &container.Container{
|
|
| 16 |
+ CommonContainer: container.CommonContainer{
|
|
| 17 |
+ ID: "container_id", |
|
| 18 |
+ Name: "container_name", |
|
| 19 |
+ Config: &containertypes.Config{
|
|
| 20 |
+ Labels: map[string]string{
|
|
| 21 |
+ "node": "1", |
|
| 22 |
+ "os": "alpine", |
|
| 23 |
+ }, |
|
| 24 |
+ }, |
|
| 25 |
+ }, |
|
| 26 |
+ } |
|
| 27 |
+ daemon := &Daemon{
|
|
| 28 |
+ EventsService: e, |
|
| 29 |
+ } |
|
| 30 |
+ daemon.LogContainerEvent(container, "create") |
|
| 31 |
+ |
|
| 32 |
+ if _, mutated := container.Config.Labels["image"]; mutated {
|
|
| 33 |
+ t.Fatalf("Expected to not mutate the container labels, got %q", container.Config.Labels)
|
|
| 34 |
+ } |
|
| 35 |
+} |
| ... | ... |
@@ -87,7 +87,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 87 | 87 |
|
| 88 | 88 |
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
| 89 | 89 |
|
| 90 |
- daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 90 |
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "untag") |
|
| 91 | 91 |
records = append(records, untaggedRecord) |
| 92 | 92 |
|
| 93 | 93 |
// If has remaining references then untag finishes the remove |
| ... | ... |
@@ -109,7 +109,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I |
| 109 | 109 |
|
| 110 | 110 |
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
| 111 | 111 |
|
| 112 |
- daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 112 |
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "untag") |
|
| 113 | 113 |
records = append(records, untaggedRecord) |
| 114 | 114 |
} |
| 115 | 115 |
} |
| ... | ... |
@@ -174,7 +174,7 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]ty |
| 174 | 174 |
|
| 175 | 175 |
untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
|
| 176 | 176 |
|
| 177 |
- daemon.EventsService.Log("untag", imgID.String(), "")
|
|
| 177 |
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "untag") |
|
| 178 | 178 |
*records = append(*records, untaggedRecord) |
| 179 | 179 |
} |
| 180 | 180 |
|
| ... | ... |
@@ -243,7 +243,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe |
| 243 | 243 |
return err |
| 244 | 244 |
} |
| 245 | 245 |
|
| 246 |
- daemon.EventsService.Log("delete", imgID.String(), "")
|
|
| 246 |
+ daemon.LogImageEvent(imgID.String(), imgID.String(), "delete") |
|
| 247 | 247 |
*records = append(*records, types.ImageDelete{Deleted: imgID.String()})
|
| 248 | 248 |
for _, removedLayer := range removedLayers {
|
| 249 | 249 |
*records = append(*records, types.ImageDelete{Deleted: removedLayer.ChainID.String()})
|
| ... | ... |
@@ -97,7 +97,7 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string |
| 97 | 97 |
} |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 |
- daemon.EventsService.Log("import", id.String(), "")
|
|
| 100 |
+ daemon.LogImageEvent(id.String(), id.String(), "import") |
|
| 101 | 101 |
outStream.Write(sf.FormatStatus("", id.String()))
|
| 102 | 102 |
return nil |
| 103 | 103 |
} |
| ... | ... |
@@ -7,7 +7,6 @@ import ( |
| 7 | 7 |
|
| 8 | 8 |
"github.com/Sirupsen/logrus" |
| 9 | 9 |
"github.com/docker/docker/api/types" |
| 10 |
- "github.com/docker/docker/daemon/events" |
|
| 11 | 10 |
"github.com/docker/docker/distribution/metadata" |
| 12 | 11 |
"github.com/docker/docker/distribution/xfer" |
| 13 | 12 |
"github.com/docker/docker/image" |
| ... | ... |
@@ -31,8 +30,8 @@ type ImagePullConfig struct {
|
| 31 | 31 |
// RegistryService is the registry service to use for TLS configuration |
| 32 | 32 |
// and endpoint lookup. |
| 33 | 33 |
RegistryService *registry.Service |
| 34 |
- // EventsService is the events service to use for logging. |
|
| 35 |
- EventsService *events.Events |
|
| 34 |
+ // ImageEventLogger notifies events for a given image |
|
| 35 |
+ ImageEventLogger func(id, name, action string) |
|
| 36 | 36 |
// MetadataStore is the storage backend for distribution-specific |
| 37 | 37 |
// metadata. |
| 38 | 38 |
MetadataStore metadata.Store |
| ... | ... |
@@ -160,7 +159,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo |
| 160 | 160 |
} |
| 161 | 161 |
} |
| 162 | 162 |
|
| 163 |
- imagePullConfig.EventsService.Log("pull", ref.String(), "")
|
|
| 163 |
+ imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull") |
|
| 164 | 164 |
return nil |
| 165 | 165 |
} |
| 166 | 166 |
|
| ... | ... |
@@ -9,7 +9,6 @@ import ( |
| 9 | 9 |
"github.com/Sirupsen/logrus" |
| 10 | 10 |
"github.com/docker/distribution/digest" |
| 11 | 11 |
"github.com/docker/docker/api/types" |
| 12 |
- "github.com/docker/docker/daemon/events" |
|
| 13 | 12 |
"github.com/docker/docker/distribution/metadata" |
| 14 | 13 |
"github.com/docker/docker/distribution/xfer" |
| 15 | 14 |
"github.com/docker/docker/image" |
| ... | ... |
@@ -35,8 +34,8 @@ type ImagePushConfig struct {
|
| 35 | 35 |
// RegistryService is the registry service to use for TLS configuration |
| 36 | 36 |
// and endpoint lookup. |
| 37 | 37 |
RegistryService *registry.Service |
| 38 |
- // EventsService is the events service to use for logging. |
|
| 39 |
- EventsService *events.Events |
|
| 38 |
+ // ImageEventLogger notifies events for a given image |
|
| 39 |
+ ImageEventLogger func(id, name, action string) |
|
| 40 | 40 |
// MetadataStore is the storage backend for distribution-specific |
| 41 | 41 |
// metadata. |
| 42 | 42 |
MetadataStore metadata.Store |
| ... | ... |
@@ -156,7 +155,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo |
| 156 | 156 |
return err |
| 157 | 157 |
} |
| 158 | 158 |
|
| 159 |
- imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
|
|
| 159 |
+ imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push") |
|
| 160 | 160 |
return nil |
| 161 | 161 |
} |
| 162 | 162 |
|
| ... | ... |
@@ -1,10 +1,16 @@ |
| 1 | 1 |
package main |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "encoding/json" |
|
| 5 |
+ "io" |
|
| 4 | 6 |
"net/http" |
| 7 |
+ "net/url" |
|
| 8 |
+ "strconv" |
|
| 9 |
+ "strings" |
|
| 5 | 10 |
"time" |
| 6 | 11 |
|
| 7 | 12 |
"github.com/docker/docker/pkg/integration/checker" |
| 13 |
+ "github.com/docker/docker/pkg/jsonmessage" |
|
| 8 | 14 |
"github.com/go-check/check" |
| 9 | 15 |
) |
| 10 | 16 |
|
| ... | ... |
@@ -28,3 +34,40 @@ func (s *DockerSuite) TestEventsApiEmptyOutput(c *check.C) {
|
| 28 | 28 |
c.Fatal("timeout waiting for events api to respond, should have responded immediately")
|
| 29 | 29 |
} |
| 30 | 30 |
} |
| 31 |
+ |
|
| 32 |
+func (s *DockerSuite) TestEventsApiBackwardsCompatible(c *check.C) {
|
|
| 33 |
+ since := daemonTime(c).Unix() |
|
| 34 |
+ ts := strconv.FormatInt(since, 10) |
|
| 35 |
+ |
|
| 36 |
+ out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top") |
|
| 37 |
+ containerID := strings.TrimSpace(out) |
|
| 38 |
+ c.Assert(waitRun(containerID), checker.IsNil) |
|
| 39 |
+ |
|
| 40 |
+ q := url.Values{}
|
|
| 41 |
+ q.Set("since", ts)
|
|
| 42 |
+ |
|
| 43 |
+ _, body, err := sockRequestRaw("GET", "/events?"+q.Encode(), nil, "")
|
|
| 44 |
+ c.Assert(err, checker.IsNil) |
|
| 45 |
+ defer body.Close() |
|
| 46 |
+ |
|
| 47 |
+ dec := json.NewDecoder(body) |
|
| 48 |
+ var containerCreateEvent *jsonmessage.JSONMessage |
|
| 49 |
+ for {
|
|
| 50 |
+ var event jsonmessage.JSONMessage |
|
| 51 |
+ if err := dec.Decode(&event); err != nil {
|
|
| 52 |
+ if err == io.EOF {
|
|
| 53 |
+ break |
|
| 54 |
+ } |
|
| 55 |
+ c.Fatal(err) |
|
| 56 |
+ } |
|
| 57 |
+ if event.Status == "create" && event.ID == containerID {
|
|
| 58 |
+ containerCreateEvent = &event |
|
| 59 |
+ break |
|
| 60 |
+ } |
|
| 61 |
+ } |
|
| 62 |
+ |
|
| 63 |
+ c.Assert(containerCreateEvent, checker.Not(checker.IsNil)) |
|
| 64 |
+ c.Assert(containerCreateEvent.Status, checker.Equals, "create") |
|
| 65 |
+ c.Assert(containerCreateEvent.ID, checker.Equals, containerID) |
|
| 66 |
+ c.Assert(containerCreateEvent.From, checker.Equals, "busybox") |
|
| 67 |
+} |
| ... | ... |
@@ -1874,6 +1874,12 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
|
| 1874 | 1874 |
testRequires(c, DaemonIsLinux) |
| 1875 | 1875 |
name := "testbuildcancellation" |
| 1876 | 1876 |
|
| 1877 |
+ observer, err := newEventObserver(c) |
|
| 1878 |
+ c.Assert(err, checker.IsNil) |
|
| 1879 |
+ err = observer.Start() |
|
| 1880 |
+ c.Assert(err, checker.IsNil) |
|
| 1881 |
+ defer observer.Stop() |
|
| 1882 |
+ |
|
| 1877 | 1883 |
// (Note: one year, will never finish) |
| 1878 | 1884 |
ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
|
| 1879 | 1885 |
if err != nil {
|
| ... | ... |
@@ -1881,15 +1887,6 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
|
| 1881 | 1881 |
} |
| 1882 | 1882 |
defer ctx.Close() |
| 1883 | 1883 |
|
| 1884 |
- eventStart := make(chan struct{})
|
|
| 1885 |
- eventDie := make(chan struct{})
|
|
| 1886 |
- |
|
| 1887 |
- observer, err := newEventObserver(c) |
|
| 1888 |
- c.Assert(err, checker.IsNil) |
|
| 1889 |
- err = observer.Start() |
|
| 1890 |
- c.Assert(err, checker.IsNil) |
|
| 1891 |
- defer observer.Stop() |
|
| 1892 |
- |
|
| 1893 | 1884 |
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".") |
| 1894 | 1885 |
buildCmd.Dir = ctx.Dir |
| 1895 | 1886 |
|
| ... | ... |
@@ -1917,26 +1914,18 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
|
| 1917 | 1917 |
c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
|
| 1918 | 1918 |
} |
| 1919 | 1919 |
|
| 1920 |
- matchStart := regexp.MustCompile(buildID + `.* start\z`) |
|
| 1921 |
- matchDie := regexp.MustCompile(buildID + `.* die\z`) |
|
| 1922 |
- |
|
| 1923 |
- matcher := func(text string) {
|
|
| 1924 |
- switch {
|
|
| 1925 |
- case matchStart.MatchString(text): |
|
| 1926 |
- close(eventStart) |
|
| 1927 |
- case matchDie.MatchString(text): |
|
| 1928 |
- close(eventDie) |
|
| 1929 |
- } |
|
| 1920 |
+ testActions := map[string]chan bool{
|
|
| 1921 |
+ "start": make(chan bool), |
|
| 1922 |
+ "die": make(chan bool), |
|
| 1930 | 1923 |
} |
| 1931 |
- go observer.Match(matcher) |
|
| 1924 |
+ |
|
| 1925 |
+ go observer.Match(matchEventLine(buildID, "container", testActions)) |
|
| 1932 | 1926 |
|
| 1933 | 1927 |
select {
|
| 1934 | 1928 |
case <-time.After(10 * time.Second): |
| 1935 | 1929 |
c.Fatal(observer.TimeoutError(buildID, "start")) |
| 1936 |
- case <-eventStart: |
|
| 1937 |
- // Proceeds from here when we see the container fly past in the |
|
| 1938 |
- // output of "docker events". |
|
| 1939 |
- // Now we know the container is running. |
|
| 1930 |
+ case <-testActions["start"]: |
|
| 1931 |
+ // ignore, done |
|
| 1940 | 1932 |
} |
| 1941 | 1933 |
|
| 1942 | 1934 |
// Send a kill to the `docker build` command. |
| ... | ... |
@@ -1952,13 +1941,10 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
|
| 1952 | 1952 |
|
| 1953 | 1953 |
select {
|
| 1954 | 1954 |
case <-time.After(10 * time.Second): |
| 1955 |
- // If we don't get here in a timely fashion, it wasn't killed. |
|
| 1956 | 1955 |
c.Fatal(observer.TimeoutError(buildID, "die")) |
| 1957 |
- case <-eventDie: |
|
| 1958 |
- // We saw the container shut down in the `docker events` stream, |
|
| 1959 |
- // as expected. |
|
| 1956 |
+ case <-testActions["die"]: |
|
| 1957 |
+ // ignore, done |
|
| 1960 | 1958 |
} |
| 1961 |
- |
|
| 1962 | 1959 |
} |
| 1963 | 1960 |
|
| 1964 | 1961 |
func (s *DockerSuite) TestBuildRm(c *check.C) {
|
| ... | ... |
@@ -6489,33 +6475,26 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
|
| 6489 | 6489 |
func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
|
| 6490 | 6490 |
testRequires(c, DaemonIsLinux) |
| 6491 | 6491 |
|
| 6492 |
- observer, err := newEventObserver(c, "--filter", "event=tag") |
|
| 6493 |
- c.Assert(err, check.IsNil) |
|
| 6494 |
- err = observer.Start() |
|
| 6495 |
- c.Assert(err, check.IsNil) |
|
| 6496 |
- defer observer.Stop() |
|
| 6492 |
+ since := daemonTime(c).Unix() |
|
| 6497 | 6493 |
|
| 6498 | 6494 |
dockerFile := `FROM busybox |
| 6499 | 6495 |
RUN echo events |
| 6500 | 6496 |
` |
| 6501 |
- _, err = buildImage("test", dockerFile, false)
|
|
| 6497 |
+ _, err := buildImage("test", dockerFile, false)
|
|
| 6502 | 6498 |
c.Assert(err, check.IsNil) |
| 6503 | 6499 |
|
| 6504 |
- matchTag := regexp.MustCompile("test:latest")
|
|
| 6505 |
- eventTag := make(chan bool) |
|
| 6506 |
- matcher := func(text string) {
|
|
| 6507 |
- if matchTag.MatchString(text) {
|
|
| 6508 |
- close(eventTag) |
|
| 6500 |
+ out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "type=image")
|
|
| 6501 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 6502 |
+ actions := eventActionsByIDAndType(c, events, "test:latest", "image") |
|
| 6503 |
+ var foundTag bool |
|
| 6504 |
+ for _, a := range actions {
|
|
| 6505 |
+ if a == "tag" {
|
|
| 6506 |
+ foundTag = true |
|
| 6507 |
+ break |
|
| 6509 | 6508 |
} |
| 6510 | 6509 |
} |
| 6511 |
- go observer.Match(matcher) |
|
| 6512 | 6510 |
|
| 6513 |
- select {
|
|
| 6514 |
- case <-time.After(10 * time.Second): |
|
| 6515 |
- c.Fatal(observer.TimeoutError("test:latest", "tag"))
|
|
| 6516 |
- case <-eventTag: |
|
| 6517 |
- // We saw the tag event as expected. |
|
| 6518 |
- } |
|
| 6511 |
+ c.Assert(foundTag, checker.True, check.Commentf("No tag event found:\n%s", out))
|
|
| 6519 | 6512 |
} |
| 6520 | 6513 |
|
| 6521 | 6514 |
// #15780 |
| ... | ... |
@@ -7,7 +7,6 @@ import ( |
| 7 | 7 |
"net/http" |
| 8 | 8 |
"os" |
| 9 | 9 |
"os/exec" |
| 10 |
- "regexp" |
|
| 11 | 10 |
"strconv" |
| 12 | 11 |
"strings" |
| 13 | 12 |
"sync" |
| ... | ... |
@@ -67,7 +66,6 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) {
|
| 67 | 67 |
} |
| 68 | 68 |
|
| 69 | 69 |
func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
|
| 70 |
- |
|
| 71 | 70 |
out, _ := dockerCmd(c, "images", "-q") |
| 72 | 71 |
image := strings.Split(out, "\n")[0] |
| 73 | 72 |
_, _, err := dockerCmdWithError("run", "--name", "testeventdie", image, "blerg")
|
| ... | ... |
@@ -75,14 +73,12 @@ func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
|
| 75 | 75 |
|
| 76 | 76 |
out, _ = dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 77 | 77 |
events := strings.Split(out, "\n") |
| 78 |
- c.Assert(len(events), checker.GreaterThan, 1) //Missing expected event |
|
| 79 |
- |
|
| 80 |
- startEvent := strings.Fields(events[len(events)-3]) |
|
| 81 |
- dieEvent := strings.Fields(events[len(events)-2]) |
|
| 82 | 78 |
|
| 83 |
- c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
|
|
| 84 |
- c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
|
|
| 79 |
+ nEvents := len(events) |
|
| 80 |
+ c.Assert(nEvents, checker.GreaterOrEqualThan, 1) //Missing expected event |
|
| 85 | 81 |
|
| 82 |
+ c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start") |
|
| 83 |
+ c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "die") |
|
| 86 | 84 |
} |
| 87 | 85 |
|
| 88 | 86 |
func (s *DockerSuite) TestEventsLimit(c *check.C) {
|
| ... | ... |
@@ -114,65 +110,83 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) {
|
| 114 | 114 |
|
| 115 | 115 |
func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
|
| 116 | 116 |
testRequires(c, DaemonIsLinux) |
| 117 |
- dockerCmd(c, "run", "--rm", "busybox", "true") |
|
| 117 |
+ containerID, _ := dockerCmd(c, "run", "--rm", "--name", "container-events-test", "busybox", "true") |
|
| 118 |
+ containerID = strings.TrimSpace(containerID) |
|
| 119 |
+ |
|
| 118 | 120 |
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 119 | 121 |
events := strings.Split(out, "\n") |
| 120 | 122 |
events = events[:len(events)-1] |
| 121 |
- c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 122 |
- createEvent := strings.Fields(events[len(events)-5]) |
|
| 123 |
- attachEvent := strings.Fields(events[len(events)-4]) |
|
| 124 |
- startEvent := strings.Fields(events[len(events)-3]) |
|
| 125 |
- dieEvent := strings.Fields(events[len(events)-2]) |
|
| 126 |
- destroyEvent := strings.Fields(events[len(events)-1]) |
|
| 127 |
- c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
|
|
| 128 |
- c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
|
|
| 129 |
- c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
|
|
| 130 |
- c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
|
|
| 131 |
- c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
|
|
| 132 | 123 |
|
| 124 |
+ nEvents := len(events) |
|
| 125 |
+ c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 126 |
+ containerEvents := eventActionsByIDAndType(c, events, "container-events-test", "container") |
|
| 127 |
+ c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
|
|
| 128 |
+ |
|
| 129 |
+ c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out)) |
|
| 130 |
+ c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out)) |
|
| 131 |
+ c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out)) |
|
| 132 |
+ c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out)) |
|
| 133 |
+ c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out)) |
|
| 133 | 134 |
} |
| 134 | 135 |
|
| 135 | 136 |
func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
|
| 136 | 137 |
testRequires(c, DaemonIsLinux) |
| 137 |
- dockerCmd(c, "run", "--rm", "busybox", "true") |
|
| 138 |
+ dockerCmd(c, "run", "--rm", "--name", "since-epoch-test", "busybox", "true") |
|
| 138 | 139 |
timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano) |
| 139 | 140 |
timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1) |
| 140 |
- out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning),
|
|
| 141 |
- fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
|
| 141 |
+ out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
|
| 142 | 142 |
events := strings.Split(out, "\n") |
| 143 | 143 |
events = events[:len(events)-1] |
| 144 |
- c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 145 |
- createEvent := strings.Fields(events[len(events)-5]) |
|
| 146 |
- attachEvent := strings.Fields(events[len(events)-4]) |
|
| 147 |
- startEvent := strings.Fields(events[len(events)-3]) |
|
| 148 |
- dieEvent := strings.Fields(events[len(events)-2]) |
|
| 149 |
- destroyEvent := strings.Fields(events[len(events)-1]) |
|
| 150 |
- c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
|
|
| 151 |
- c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
|
|
| 152 |
- c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
|
|
| 153 |
- c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
|
|
| 154 |
- c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
|
|
| 155 | 144 |
|
| 145 |
+ nEvents := len(events) |
|
| 146 |
+ c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 147 |
+ containerEvents := eventActionsByIDAndType(c, events, "since-epoch-test", "container") |
|
| 148 |
+ c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
|
|
| 149 |
+ |
|
| 150 |
+ c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out)) |
|
| 151 |
+ c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out)) |
|
| 152 |
+ c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out)) |
|
| 153 |
+ c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out)) |
|
| 154 |
+ c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out)) |
|
| 156 | 155 |
} |
| 157 | 156 |
|
| 158 | 157 |
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
|
| 159 | 158 |
testRequires(c, DaemonIsLinux) |
| 159 |
+ |
|
| 160 |
+ observer, err := newEventObserver(c) |
|
| 161 |
+ c.Assert(err, checker.IsNil) |
|
| 162 |
+ err = observer.Start() |
|
| 163 |
+ c.Assert(err, checker.IsNil) |
|
| 164 |
+ defer observer.Stop() |
|
| 165 |
+ |
|
| 160 | 166 |
name := "testimageevents" |
| 161 |
- _, err := buildImage(name, |
|
| 167 |
+ imageID, err := buildImage(name, |
|
| 162 | 168 |
`FROM scratch |
| 163 | 169 |
MAINTAINER "docker"`, |
| 164 | 170 |
true) |
| 165 | 171 |
c.Assert(err, checker.IsNil) |
| 166 | 172 |
c.Assert(deleteImages(name), checker.IsNil) |
| 167 |
- out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
|
| 168 |
- events := strings.Split(out, "\n") |
|
| 169 | 173 |
|
| 170 |
- events = events[:len(events)-1] |
|
| 171 |
- c.Assert(len(events), checker.GreaterOrEqualThan, 2) //Missing expected event |
|
| 172 |
- untagEvent := strings.Fields(events[len(events)-2]) |
|
| 173 |
- deleteEvent := strings.Fields(events[len(events)-1]) |
|
| 174 |
- c.Assert(untagEvent[len(untagEvent)-1], checker.Equals, "untag", check.Commentf("untag should be untag, not %#v", untagEvent))
|
|
| 175 |
- c.Assert(deleteEvent[len(deleteEvent)-1], checker.Equals, "delete", check.Commentf("untag should be delete, not %#v", untagEvent))
|
|
| 174 |
+ testActions := map[string]chan bool{
|
|
| 175 |
+ "untag": make(chan bool), |
|
| 176 |
+ "delete": make(chan bool), |
|
| 177 |
+ } |
|
| 178 |
+ |
|
| 179 |
+ go observer.Match(matchEventLine(imageID, "image", testActions)) |
|
| 180 |
+ |
|
| 181 |
+ select {
|
|
| 182 |
+ case <-time.After(10 * time.Second): |
|
| 183 |
+ c.Fatal(observer.TimeoutError(imageID, "untag")) |
|
| 184 |
+ case <-testActions["untag"]: |
|
| 185 |
+ // ignore, done |
|
| 186 |
+ } |
|
| 187 |
+ |
|
| 188 |
+ select {
|
|
| 189 |
+ case <-time.After(10 * time.Second): |
|
| 190 |
+ c.Fatal(observer.TimeoutError(imageID, "delete")) |
|
| 191 |
+ case <-testActions["delete"]: |
|
| 192 |
+ // ignore, done |
|
| 193 |
+ } |
|
| 176 | 194 |
} |
| 177 | 195 |
|
| 178 | 196 |
func (s *DockerSuite) TestEventsImageTag(c *check.C) {
|
| ... | ... |
@@ -189,10 +203,11 @@ func (s *DockerSuite) TestEventsImageTag(c *check.C) {
|
| 189 | 189 |
events := strings.Split(strings.TrimSpace(out), "\n") |
| 190 | 190 |
c.Assert(events, checker.HasLen, 1, check.Commentf("was expecting 1 event. out=%s", out))
|
| 191 | 191 |
event := strings.TrimSpace(events[0]) |
| 192 |
- expectedStr := image + ": tag" |
|
| 193 |
- |
|
| 194 |
- c.Assert(event, checker.HasSuffix, expectedStr, check.Commentf("wrong event format. expected='%s' got=%s", expectedStr, event))
|
|
| 195 | 192 |
|
| 193 |
+ matches := parseEventText(event) |
|
| 194 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 195 |
+ c.Assert(matchEventID(matches, image), checker.True, check.Commentf("matches: %v\nout:\n%s", matches, out))
|
|
| 196 |
+ c.Assert(matches["action"], checker.Equals, "tag") |
|
| 196 | 197 |
} |
| 197 | 198 |
|
| 198 | 199 |
func (s *DockerSuite) TestEventsImagePull(c *check.C) {
|
| ... | ... |
@@ -208,68 +223,46 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) {
|
| 208 | 208 |
|
| 209 | 209 |
events := strings.Split(strings.TrimSpace(out), "\n") |
| 210 | 210 |
event := strings.TrimSpace(events[len(events)-1]) |
| 211 |
- |
|
| 212 |
- c.Assert(event, checker.HasSuffix, "hello-world:latest: pull", check.Commentf("Missing pull event - got:%q", event))
|
|
| 211 |
+ matches := parseEventText(event) |
|
| 212 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 213 |
+ c.Assert(matches["id"], checker.Equals, "hello-world:latest") |
|
| 214 |
+ c.Assert(matches["action"], checker.Equals, "pull") |
|
| 213 | 215 |
|
| 214 | 216 |
} |
| 215 | 217 |
|
| 216 | 218 |
func (s *DockerSuite) TestEventsImageImport(c *check.C) {
|
| 217 | 219 |
testRequires(c, DaemonIsLinux) |
| 218 | 220 |
|
| 219 |
- observer, err := newEventObserver(c) |
|
| 220 |
- c.Assert(err, checker.IsNil) |
|
| 221 |
- |
|
| 222 |
- err = observer.Start() |
|
| 223 |
- c.Assert(err, checker.IsNil) |
|
| 224 |
- defer observer.Stop() |
|
| 225 |
- |
|
| 226 | 221 |
out, _ := dockerCmd(c, "run", "-d", "busybox", "true") |
| 227 | 222 |
cleanedContainerID := strings.TrimSpace(out) |
| 228 | 223 |
|
| 229 |
- out, _, err = runCommandPipelineWithOutput( |
|
| 224 |
+ since := daemonTime(c).Unix() |
|
| 225 |
+ out, _, err := runCommandPipelineWithOutput( |
|
| 230 | 226 |
exec.Command(dockerBinary, "export", cleanedContainerID), |
| 231 | 227 |
exec.Command(dockerBinary, "import", "-"), |
| 232 | 228 |
) |
| 233 | 229 |
c.Assert(err, checker.IsNil, check.Commentf("import failed with output: %q", out))
|
| 234 | 230 |
imageRef := strings.TrimSpace(out) |
| 235 | 231 |
|
| 236 |
- eventImport := make(chan bool) |
|
| 237 |
- matchImport := regexp.MustCompile(imageRef + `: import\z`) |
|
| 238 |
- matcher := func(text string) {
|
|
| 239 |
- if matchImport.MatchString(text) {
|
|
| 240 |
- close(eventImport) |
|
| 241 |
- } |
|
| 242 |
- } |
|
| 243 |
- go observer.Match(matcher) |
|
| 244 |
- |
|
| 245 |
- select {
|
|
| 246 |
- case <-time.After(5 * time.Second): |
|
| 247 |
- c.Fatal(observer.TimeoutError(imageRef, "import")) |
|
| 248 |
- case <-eventImport: |
|
| 249 |
- // ignore, done |
|
| 250 |
- } |
|
| 232 |
+ out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=import")
|
|
| 233 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 234 |
+ c.Assert(events, checker.HasLen, 1) |
|
| 235 |
+ matches := parseEventText(events[0]) |
|
| 236 |
+ c.Assert(matches["id"], checker.Equals, imageRef, check.Commentf("matches: %v\nout:\n%s\n", matches, out))
|
|
| 237 |
+ c.Assert(matches["action"], checker.Equals, "import", check.Commentf("matches: %v\nout:\n%s\n", matches, out))
|
|
| 251 | 238 |
} |
| 252 | 239 |
|
| 253 | 240 |
func (s *DockerSuite) TestEventsFilters(c *check.C) {
|
| 254 | 241 |
testRequires(c, DaemonIsLinux) |
| 255 |
- parseEvents := func(out, match string) {
|
|
| 256 |
- events := strings.Split(out, "\n") |
|
| 257 |
- events = events[:len(events)-1] |
|
| 258 |
- for _, event := range events {
|
|
| 259 |
- eventFields := strings.Fields(event) |
|
| 260 |
- eventName := eventFields[len(eventFields)-1] |
|
| 261 |
- c.Assert(eventName, checker.Matches, match) |
|
| 262 |
- } |
|
| 263 |
- } |
|
| 264 | 242 |
|
| 265 | 243 |
since := daemonTime(c).Unix() |
| 266 | 244 |
dockerCmd(c, "run", "--rm", "busybox", "true") |
| 267 | 245 |
dockerCmd(c, "run", "--rm", "busybox", "true") |
| 268 | 246 |
out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")
|
| 269 |
- parseEvents(out, "die") |
|
| 247 |
+ parseEvents(c, out, "die") |
|
| 270 | 248 |
|
| 271 | 249 |
out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")
|
| 272 |
- parseEvents(out, "((die)|(start))") |
|
| 250 |
+ parseEvents(c, out, "die|start") |
|
| 273 | 251 |
|
| 274 | 252 |
// make sure we at least got 2 start events |
| 275 | 253 |
count := strings.Count(out, "start") |
| ... | ... |
@@ -385,15 +378,9 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
|
| 385 | 385 |
return fmt.Errorf("expected 4 events, got %v", events)
|
| 386 | 386 |
} |
| 387 | 387 |
for _, event := range events {
|
| 388 |
- e := strings.Fields(event) |
|
| 389 |
- if len(e) < 3 {
|
|
| 390 |
- return fmt.Errorf("got malformed event: %s", event)
|
|
| 391 |
- } |
|
| 392 |
- |
|
| 393 |
- // Check the id |
|
| 394 |
- parsedID := strings.TrimSuffix(e[1], ":") |
|
| 395 |
- if parsedID != id {
|
|
| 396 |
- return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID)
|
|
| 388 |
+ matches := parseEventText(event) |
|
| 389 |
+ if !matchEventID(matches, id) {
|
|
| 390 |
+ return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, matches["id"])
|
|
| 397 | 391 |
} |
| 398 | 392 |
} |
| 399 | 393 |
return nil |
| ... | ... |
@@ -450,21 +437,21 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) {
|
| 450 | 450 |
select {
|
| 451 | 451 |
case <-time.After(5 * time.Second): |
| 452 | 452 |
c.Fatal(observer.TimeoutError(containerID, "create")) |
| 453 |
- case <-eventCreate: |
|
| 453 |
+ case <-testActions["create"]: |
|
| 454 | 454 |
// ignore, done |
| 455 | 455 |
} |
| 456 | 456 |
|
| 457 | 457 |
select {
|
| 458 | 458 |
case <-time.After(5 * time.Second): |
| 459 | 459 |
c.Fatal(observer.TimeoutError(containerID, "start")) |
| 460 |
- case <-eventStart: |
|
| 460 |
+ case <-testActions["start"]: |
|
| 461 | 461 |
// ignore, done |
| 462 | 462 |
} |
| 463 | 463 |
|
| 464 | 464 |
select {
|
| 465 | 465 |
case <-time.After(5 * time.Second): |
| 466 | 466 |
c.Fatal(observer.TimeoutError(containerID, "die")) |
| 467 |
- case <-eventDie: |
|
| 467 |
+ case <-testActions["die"]: |
|
| 468 | 468 |
// ignore, done |
| 469 | 469 |
} |
| 470 | 470 |
|
| ... | ... |
@@ -490,7 +477,7 @@ func (s *DockerSuite) TestEventsCommit(c *check.C) {
|
| 490 | 490 |
dockerCmd(c, "stop", cID) |
| 491 | 491 |
|
| 492 | 492 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since))) |
| 493 |
- c.Assert(out, checker.Contains, " commit\n", check.Commentf("Missing 'commit' log event"))
|
|
| 493 |
+ c.Assert(out, checker.Contains, "commit", check.Commentf("Missing 'commit' log event"))
|
|
| 494 | 494 |
} |
| 495 | 495 |
|
| 496 | 496 |
func (s *DockerSuite) TestEventsCopy(c *check.C) {
|
| ... | ... |
@@ -515,12 +502,12 @@ func (s *DockerSuite) TestEventsCopy(c *check.C) {
|
| 515 | 515 |
dockerCmd(c, "cp", "cptest:/tmp/file", tempFile.Name()) |
| 516 | 516 |
|
| 517 | 517 |
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since))) |
| 518 |
- c.Assert(out, checker.Contains, " archive-path\n", check.Commentf("Missing 'archive-path' log event\n"))
|
|
| 518 |
+ c.Assert(out, checker.Contains, "archive-path", check.Commentf("Missing 'archive-path' log event\n"))
|
|
| 519 | 519 |
|
| 520 | 520 |
dockerCmd(c, "cp", tempFile.Name(), "cptest:/tmp/filecopy") |
| 521 | 521 |
|
| 522 | 522 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since))) |
| 523 |
- c.Assert(out, checker.Contains, " extract-to-dir\n", check.Commentf("Missing 'extract-to-dir' log event"))
|
|
| 523 |
+ c.Assert(out, checker.Contains, "extract-to-dir", check.Commentf("Missing 'extract-to-dir' log event"))
|
|
| 524 | 524 |
} |
| 525 | 525 |
|
| 526 | 526 |
func (s *DockerSuite) TestEventsResize(c *check.C) {
|
| ... | ... |
@@ -539,7 +526,7 @@ func (s *DockerSuite) TestEventsResize(c *check.C) {
|
| 539 | 539 |
dockerCmd(c, "stop", cID) |
| 540 | 540 |
|
| 541 | 541 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since))) |
| 542 |
- c.Assert(out, checker.Contains, " resize\n", check.Commentf("Missing 'resize' log event"))
|
|
| 542 |
+ c.Assert(out, checker.Contains, "resize", check.Commentf("Missing 'resize' log event"))
|
|
| 543 | 543 |
} |
| 544 | 544 |
|
| 545 | 545 |
func (s *DockerSuite) TestEventsAttach(c *check.C) {
|
| ... | ... |
@@ -571,7 +558,7 @@ func (s *DockerSuite) TestEventsAttach(c *check.C) {
|
| 571 | 571 |
dockerCmd(c, "stop", cID) |
| 572 | 572 |
|
| 573 | 573 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since))) |
| 574 |
- c.Assert(out, checker.Contains, " attach\n", check.Commentf("Missing 'attach' log event"))
|
|
| 574 |
+ c.Assert(out, checker.Contains, "attach", check.Commentf("Missing 'attach' log event"))
|
|
| 575 | 575 |
} |
| 576 | 576 |
|
| 577 | 577 |
func (s *DockerSuite) TestEventsRename(c *check.C) {
|
| ... | ... |
@@ -582,7 +569,7 @@ func (s *DockerSuite) TestEventsRename(c *check.C) {
|
| 582 | 582 |
dockerCmd(c, "rename", "oldName", "newName") |
| 583 | 583 |
|
| 584 | 584 |
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=newName", "--until="+strconv.Itoa(int(since))) |
| 585 |
- c.Assert(out, checker.Contains, " rename\n", check.Commentf("Missing 'rename' log event\n"))
|
|
| 585 |
+ c.Assert(out, checker.Contains, "rename", check.Commentf("Missing 'rename' log event\n"))
|
|
| 586 | 586 |
} |
| 587 | 587 |
|
| 588 | 588 |
func (s *DockerSuite) TestEventsTop(c *check.C) {
|
| ... | ... |
@@ -597,7 +584,7 @@ func (s *DockerSuite) TestEventsTop(c *check.C) {
|
| 597 | 597 |
dockerCmd(c, "stop", cID) |
| 598 | 598 |
|
| 599 | 599 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since))) |
| 600 |
- c.Assert(out, checker.Contains, " top\n", check.Commentf("Missing 'top' log event"))
|
|
| 600 |
+ c.Assert(out, checker.Contains, " top", check.Commentf("Missing 'top' log event"))
|
|
| 601 | 601 |
} |
| 602 | 602 |
|
| 603 | 603 |
// #13753 |
| ... | ... |
@@ -624,5 +611,5 @@ func (s *DockerRegistrySuite) TestEventsImageFilterPush(c *check.C) {
|
| 624 | 624 |
dockerCmd(c, "push", repoName) |
| 625 | 625 |
|
| 626 | 626 |
out, _ = dockerCmd(c, "events", "--since=0", "-f", "image="+repoName, "-f", "event=push", "--until="+strconv.Itoa(int(since))) |
| 627 |
- c.Assert(out, checker.Contains, repoName+": push\n", check.Commentf("Missing 'push' log event"))
|
|
| 627 |
+ c.Assert(out, checker.Contains, repoName, check.Commentf("Missing 'push' log event for %s", repoName))
|
|
| 628 | 628 |
} |
| ... | ... |
@@ -65,18 +65,14 @@ func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) {
|
| 65 | 65 |
|
| 66 | 66 |
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomFalse", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 67 | 67 |
events := strings.Split(strings.TrimSuffix(out, "\n"), "\n") |
| 68 |
- c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 69 |
- |
|
| 70 |
- createEvent := strings.Fields(events[len(events)-5]) |
|
| 71 |
- attachEvent := strings.Fields(events[len(events)-4]) |
|
| 72 |
- startEvent := strings.Fields(events[len(events)-3]) |
|
| 73 |
- oomEvent := strings.Fields(events[len(events)-2]) |
|
| 74 |
- dieEvent := strings.Fields(events[len(events)-1]) |
|
| 75 |
- c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
|
|
| 76 |
- c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
|
|
| 77 |
- c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
|
|
| 78 |
- c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
|
|
| 79 |
- c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
|
|
| 68 |
+ nEvents := len(events) |
|
| 69 |
+ |
|
| 70 |
+ c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event |
|
| 71 |
+ c.Assert(parseEventAction(c, events[nEvents-5]), checker.Equals, "create") |
|
| 72 |
+ c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "attach") |
|
| 73 |
+ c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start") |
|
| 74 |
+ c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "oom") |
|
| 75 |
+ c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "die") |
|
| 80 | 76 |
} |
| 81 | 77 |
|
| 82 | 78 |
func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
|
| ... | ... |
@@ -98,17 +94,13 @@ func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
|
| 98 | 98 |
|
| 99 | 99 |
out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomTrue", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 100 | 100 |
events := strings.Split(strings.TrimSuffix(out, "\n"), "\n") |
| 101 |
- c.Assert(len(events), checker.GreaterOrEqualThan, 4) //Missing expected event |
|
| 101 |
+ nEvents := len(events) |
|
| 102 |
+ c.Assert(nEvents, checker.GreaterOrEqualThan, 4) //Missing expected event |
|
| 102 | 103 |
|
| 103 |
- createEvent := strings.Fields(events[len(events)-4]) |
|
| 104 |
- attachEvent := strings.Fields(events[len(events)-3]) |
|
| 105 |
- startEvent := strings.Fields(events[len(events)-2]) |
|
| 106 |
- oomEvent := strings.Fields(events[len(events)-1]) |
|
| 107 |
- |
|
| 108 |
- c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
|
|
| 109 |
- c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
|
|
| 110 |
- c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
|
|
| 111 |
- c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
|
|
| 104 |
+ c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "create") |
|
| 105 |
+ c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "attach") |
|
| 106 |
+ c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "start") |
|
| 107 |
+ c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "oom") |
|
| 112 | 108 |
|
| 113 | 109 |
out, _ = dockerCmd(c, "inspect", "-f", "{{.State.Status}}", "oomTrue")
|
| 114 | 110 |
c.Assert(strings.TrimSpace(out), checker.Equals, "running", check.Commentf("container should be still running"))
|
| ... | ... |
@@ -116,17 +108,17 @@ func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
|
| 116 | 116 |
} |
| 117 | 117 |
|
| 118 | 118 |
// #18453 |
| 119 |
-func (s *DockerSuite) TestEventsContainerFilter(c *check.C) {
|
|
| 119 |
+func (s *DockerSuite) TestEventsContainerFilterByName(c *check.C) {
|
|
| 120 | 120 |
testRequires(c, DaemonIsLinux) |
| 121 |
- out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top") |
|
| 122 |
- c1 := strings.TrimSpace(out) |
|
| 123 |
- waitRun(c1) |
|
| 124 |
- out, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top") |
|
| 125 |
- c2 := strings.TrimSpace(out) |
|
| 126 |
- waitRun(c2) |
|
| 127 |
- out, _ = dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
|
| 128 |
- c.Assert(out, checker.Contains, c1, check.Commentf("Missing event of container (foo)"))
|
|
| 129 |
- c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf("Should not contain event of container (bar)"))
|
|
| 121 |
+ cOut, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top") |
|
| 122 |
+ c1 := strings.TrimSpace(cOut) |
|
| 123 |
+ waitRun("foo")
|
|
| 124 |
+ cOut, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top") |
|
| 125 |
+ c2 := strings.TrimSpace(cOut) |
|
| 126 |
+ waitRun("bar")
|
|
| 127 |
+ out, _ := dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
|
| 128 |
+ c.Assert(out, checker.Contains, c1, check.Commentf(out)) |
|
| 129 |
+ c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf(out)) |
|
| 130 | 130 |
} |
| 131 | 131 |
|
| 132 | 132 |
// #18453 |
| ... | ... |
@@ -23,15 +23,11 @@ func (s *DockerSuite) TestPause(c *check.C) {
|
| 23 | 23 |
dockerCmd(c, "unpause", name) |
| 24 | 24 |
|
| 25 | 25 |
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 26 |
- events := strings.Split(out, "\n") |
|
| 27 |
- c.Assert(len(events) > 1, checker.Equals, true) |
|
| 28 |
- |
|
| 29 |
- pauseEvent := strings.Fields(events[len(events)-3]) |
|
| 30 |
- unpauseEvent := strings.Fields(events[len(events)-2]) |
|
| 31 |
- |
|
| 32 |
- c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause") |
|
| 33 |
- c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause") |
|
| 26 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 27 |
+ actions := eventActionsByIDAndType(c, events, name, "container") |
|
| 34 | 28 |
|
| 29 |
+ c.Assert(actions[len(actions)-2], checker.Equals, "pause") |
|
| 30 |
+ c.Assert(actions[len(actions)-1], checker.Equals, "unpause") |
|
| 35 | 31 |
} |
| 36 | 32 |
|
| 37 | 33 |
func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
|
| ... | ... |
@@ -53,21 +49,12 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
|
| 53 | 53 |
dockerCmd(c, append([]string{"unpause"}, containers...)...)
|
| 54 | 54 |
|
| 55 | 55 |
out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
|
| 56 |
- events := strings.Split(out, "\n") |
|
| 57 |
- c.Assert(len(events) > len(containers)*3-2, checker.Equals, true) |
|
| 56 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 58 | 57 |
|
| 59 |
- pauseEvents := make([][]string, len(containers)) |
|
| 60 |
- unpauseEvents := make([][]string, len(containers)) |
|
| 61 |
- for i := range containers {
|
|
| 62 |
- pauseEvents[i] = strings.Fields(events[len(events)-len(containers)*2-1+i]) |
|
| 63 |
- unpauseEvents[i] = strings.Fields(events[len(events)-len(containers)-1+i]) |
|
| 64 |
- } |
|
| 58 |
+ for _, name := range containers {
|
|
| 59 |
+ actions := eventActionsByIDAndType(c, events, name, "container") |
|
| 65 | 60 |
|
| 66 |
- for _, pauseEvent := range pauseEvents {
|
|
| 67 |
- c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause") |
|
| 68 |
- } |
|
| 69 |
- for _, unpauseEvent := range unpauseEvents {
|
|
| 70 |
- c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause") |
|
| 61 |
+ c.Assert(actions[len(actions)-2], checker.Equals, "pause") |
|
| 62 |
+ c.Assert(actions[len(actions)-1], checker.Equals, "unpause") |
|
| 71 | 63 |
} |
| 72 |
- |
|
| 73 | 64 |
} |
| ... | ... |
@@ -7,10 +7,24 @@ import ( |
| 7 | 7 |
"io" |
| 8 | 8 |
"os/exec" |
| 9 | 9 |
"strconv" |
| 10 |
+ "strings" |
|
| 10 | 11 |
|
| 12 |
+ "github.com/docker/docker/pkg/integration/checker" |
|
| 11 | 13 |
"github.com/go-check/check" |
| 12 | 14 |
) |
| 13 | 15 |
|
| 16 |
+var ( |
|
| 17 |
+ reTimestamp = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{9}(:?(:?(:?-|\+)\d{2}:\d{2})|Z)`
|
|
| 18 |
+ reEventType = `(?P<eventType>\w+)` |
|
| 19 |
+ reAction = `(?P<action>\w+)` |
|
| 20 |
+ reID = `(?P<id>[^\s]+)` |
|
| 21 |
+ reAttributes = `(\s\((?P<attributes>[^\)]+)\))?` |
|
| 22 |
+ reString = fmt.Sprintf(`\A%s\s%s\s%s\s%s%s\z`, reTimestamp, reEventType, reAction, reID, reAttributes) |
|
| 23 |
+ |
|
| 24 |
+ // eventCliRegexp is a regular expression that matches all possible event outputs in the cli |
|
| 25 |
+ eventCliRegexp = regexp.MustCompile(reString) |
|
| 26 |
+) |
|
| 27 |
+ |
|
| 14 | 28 |
// eventMatcher is a function that tries to match an event input. |
| 15 | 29 |
type eventMatcher func(text string) |
| 16 | 30 |
|
| ... | ... |
@@ -25,7 +39,11 @@ type eventObserver struct {
|
| 25 | 25 |
// without running it. Users must call `eventObserver.Start` to start the command. |
| 26 | 26 |
func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
|
| 27 | 27 |
since := daemonTime(c).Unix() |
| 28 |
+ return newEventObserverWithBacklog(c, since, args...) |
|
| 29 |
+} |
|
| 28 | 30 |
|
| 31 |
+// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return. |
|
| 32 |
+func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
|
|
| 29 | 33 |
cmdArgs := []string{"events", "--since", strconv.FormatInt(since, 10)}
|
| 30 | 34 |
if len(args) > 0 {
|
| 31 | 35 |
cmdArgs = append(cmdArgs, args...) |
| ... | ... |
@@ -51,6 +69,7 @@ func (e *eventObserver) Start() error {
|
| 51 | 51 |
// Stop stops the events command. |
| 52 | 52 |
func (e *eventObserver) Stop() {
|
| 53 | 53 |
e.command.Process.Kill() |
| 54 |
+ e.command.Process.Release() |
|
| 54 | 55 |
} |
| 55 | 56 |
|
| 56 | 57 |
// Match tries to match the events output with a given matcher. |
| ... | ... |
@@ -76,3 +95,103 @@ func (e *eventObserver) TimeoutError(id, event string) error {
|
| 76 | 76 |
func (e *eventObserver) output() string {
|
| 77 | 77 |
return e.buffer.String() |
| 78 | 78 |
} |
| 79 |
+ |
|
| 80 |
+// matchEventLine matches a text with the event regular expression. |
|
| 81 |
+// It returns the action and true if the regular expression matches with the given id and event type. |
|
| 82 |
+// It returns an empty string and false if there is no match. |
|
| 83 |
+func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
|
|
| 84 |
+ return func(text string) {
|
|
| 85 |
+ matches := parseEventText(text) |
|
| 86 |
+ if matches == nil {
|
|
| 87 |
+ return |
|
| 88 |
+ } |
|
| 89 |
+ |
|
| 90 |
+ if matchIDAndEventType(matches, id, eventType) {
|
|
| 91 |
+ if ch, ok := actions[matches["action"]]; ok {
|
|
| 92 |
+ close(ch) |
|
| 93 |
+ } |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 98 |
+// parseEventText parses a line of events coming from the cli and returns |
|
| 99 |
+// the matchers in a map. |
|
| 100 |
+func parseEventText(text string) map[string]string {
|
|
| 101 |
+ matches := eventCliRegexp.FindAllStringSubmatch(text, -1) |
|
| 102 |
+ if len(matches) == 0 {
|
|
| 103 |
+ return nil |
|
| 104 |
+ } |
|
| 105 |
+ |
|
| 106 |
+ names := eventCliRegexp.SubexpNames() |
|
| 107 |
+ md := map[string]string{}
|
|
| 108 |
+ for i, n := range matches[0] {
|
|
| 109 |
+ md[names[i]] = n |
|
| 110 |
+ } |
|
| 111 |
+ return md |
|
| 112 |
+} |
|
| 113 |
+ |
|
| 114 |
+// parseEventAction parses an event text and returns the action. |
|
| 115 |
+// It fails if the text is not in the event format. |
|
| 116 |
+func parseEventAction(c *check.C, text string) string {
|
|
| 117 |
+ matches := parseEventText(text) |
|
| 118 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 119 |
+ return matches["action"] |
|
| 120 |
+} |
|
| 121 |
+ |
|
| 122 |
+// eventActionsByIDAndType returns the actions for a given id and type. |
|
| 123 |
+// It fails if the text is not in the event format. |
|
| 124 |
+func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
|
|
| 125 |
+ var filtered []string |
|
| 126 |
+ for _, event := range events {
|
|
| 127 |
+ matches := parseEventText(event) |
|
| 128 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 129 |
+ if matchIDAndEventType(matches, id, eventType) {
|
|
| 130 |
+ filtered = append(filtered, matches["action"]) |
|
| 131 |
+ } |
|
| 132 |
+ } |
|
| 133 |
+ return filtered |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+// matchIDAndEventType returns true if an event matches a given id and type. |
|
| 137 |
+// It also resolves names in the event attributes if the id doesn't match. |
|
| 138 |
+func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
|
|
| 139 |
+ return matchEventID(matches, id) && matches["eventType"] == eventType |
|
| 140 |
+} |
|
| 141 |
+ |
|
| 142 |
+func matchEventID(matches map[string]string, id string) bool {
|
|
| 143 |
+ matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id) |
|
| 144 |
+ if !matchID && matches["attributes"] != "" {
|
|
| 145 |
+ // try matching a name in the attributes |
|
| 146 |
+ attributes := map[string]string{}
|
|
| 147 |
+ for _, a := range strings.Split(matches["attributes"], ", ") {
|
|
| 148 |
+ kv := strings.Split(a, "=") |
|
| 149 |
+ attributes[kv[0]] = kv[1] |
|
| 150 |
+ } |
|
| 151 |
+ matchID = attributes["name"] == id |
|
| 152 |
+ } |
|
| 153 |
+ return matchID |
|
| 154 |
+} |
|
| 155 |
+ |
|
| 156 |
+func parseEvents(c *check.C, out, match string) {
|
|
| 157 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 158 |
+ for _, event := range events {
|
|
| 159 |
+ matches := parseEventText(event) |
|
| 160 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 161 |
+ matched, err := regexp.MatchString(match, matches["action"]) |
|
| 162 |
+ c.Assert(err, checker.IsNil) |
|
| 163 |
+ c.Assert(matched, checker.True) |
|
| 164 |
+ } |
|
| 165 |
+} |
|
| 166 |
+ |
|
| 167 |
+func parseEventsWithID(c *check.C, out, match, id string) {
|
|
| 168 |
+ events := strings.Split(strings.TrimSpace(out), "\n") |
|
| 169 |
+ for _, event := range events {
|
|
| 170 |
+ matches := parseEventText(event) |
|
| 171 |
+ c.Assert(matches, checker.Not(checker.IsNil)) |
|
| 172 |
+ c.Assert(matchEventID(matches, id), checker.True) |
|
| 173 |
+ |
|
| 174 |
+ matched, err := regexp.MatchString(match, matches["action"]) |
|
| 175 |
+ c.Assert(err, checker.IsNil) |
|
| 176 |
+ c.Assert(matched, checker.True) |
|
| 177 |
+ } |
|
| 178 |
+} |