mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Add event types.
- 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>
This commit is contained in:
		
							parent
							
								
									687ef00562
								
							
						
					
					
						commit
						72f1881df1
					
				
					 23 changed files with 665 additions and 317 deletions
				
			
		| 
						 | 
				
			
			@ -1,11 +1,18 @@
 | 
			
		|||
package client
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	eventtypes "github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	Cli "github.com/docker/docker/cli"
 | 
			
		||||
	"github.com/docker/docker/opts"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonlog"
 | 
			
		||||
	flag "github.com/docker/docker/pkg/mflag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -46,5 +53,43 @@ func (cli *DockerCli) CmdEvents(args ...string) error {
 | 
			
		|||
	}
 | 
			
		||||
	defer responseBody.Close()
 | 
			
		||||
 | 
			
		||||
	return jsonmessage.DisplayJSONMessagesStream(responseBody, cli.out, cli.outFd, cli.isTerminalOut)
 | 
			
		||||
	return streamEvents(responseBody, cli.out)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// streamEvents decodes prints the incoming events in the provided output.
 | 
			
		||||
func streamEvents(input io.Reader, output io.Writer) error {
 | 
			
		||||
	dec := json.NewDecoder(input)
 | 
			
		||||
	for {
 | 
			
		||||
		var event eventtypes.Message
 | 
			
		||||
		if err := dec.Decode(&event); err != nil {
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		printOutput(event, output)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// printOutput prints all types of event information.
 | 
			
		||||
// Each output includes the event type, actor id, name and action.
 | 
			
		||||
// Actor attributes are printed at the end if the actor has any.
 | 
			
		||||
func printOutput(event eventtypes.Message, output io.Writer) {
 | 
			
		||||
	if event.TimeNano != 0 {
 | 
			
		||||
		fmt.Fprintf(output, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
 | 
			
		||||
	} else if event.Time != 0 {
 | 
			
		||||
		fmt.Fprintf(output, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmt.Fprintf(output, "%s %s %s", event.Type, event.Action, event.Actor.ID)
 | 
			
		||||
 | 
			
		||||
	if len(event.Actor.Attributes) > 0 {
 | 
			
		||||
		var attrs []string
 | 
			
		||||
		for k, v := range event.Actor.Attributes {
 | 
			
		||||
			attrs = append(attrs, fmt.Sprintf("%s=%s", k, v))
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintf(output, " (%s)", strings.Join(attrs, ", "))
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Fprint(output, "\n")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@ package system
 | 
			
		|||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Backend is the methods that need to be implemented to provide
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ import (
 | 
			
		|||
type Backend interface {
 | 
			
		||||
	SystemInfo() (*types.Info, error)
 | 
			
		||||
	SystemVersion() types.Version
 | 
			
		||||
	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]*jsonmessage.JSONMessage, chan interface{})
 | 
			
		||||
	SubscribeToEvents(since, sinceNano int64, ef filters.Args) ([]events.Message, chan interface{})
 | 
			
		||||
	UnsubscribeFromEvents(chan interface{})
 | 
			
		||||
	AuthenticateToRegistry(authConfig *types.AuthConfig) (string, error)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,10 +9,10 @@ import (
 | 
			
		|||
	"github.com/docker/docker/api"
 | 
			
		||||
	"github.com/docker/docker/api/server/httputils"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	timetypes "github.com/docker/docker/api/types/time"
 | 
			
		||||
	"github.com/docker/docker/pkg/ioutils"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"golang.org/x/net/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -98,8 +98,9 @@ func (s *systemRouter) getEvents(ctx context.Context, w http.ResponseWriter, r *
 | 
			
		|||
	for {
 | 
			
		||||
		select {
 | 
			
		||||
		case ev := <-l:
 | 
			
		||||
			jev, ok := ev.(*jsonmessage.JSONMessage)
 | 
			
		||||
			jev, ok := ev.(events.Message)
 | 
			
		||||
			if !ok {
 | 
			
		||||
				logrus.Warnf("unexpected event message: %q", ev)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if err := enc.Encode(jev); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								api/types/events/events.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								api/types/events/events.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package events
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// ContainerEventType is the event type that containers generate
 | 
			
		||||
	ContainerEventType = "container"
 | 
			
		||||
	// ImageEventType is the event type that images generate
 | 
			
		||||
	ImageEventType = "image"
 | 
			
		||||
	// VolumeEventType is the event type that volumes generate
 | 
			
		||||
	VolumeEventType = "volume"
 | 
			
		||||
	// NetworkEventType is the event type that networks generate
 | 
			
		||||
	NetworkEventType = "network"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Actor describes something that generates events,
 | 
			
		||||
// like a container, or a network, or a volume.
 | 
			
		||||
// It has a defined name and a set or attributes.
 | 
			
		||||
// The container attributes are its labels, other actors
 | 
			
		||||
// can generate these attributes from other properties.
 | 
			
		||||
type Actor struct {
 | 
			
		||||
	ID         string
 | 
			
		||||
	Attributes map[string]string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Message represents the information an event contains
 | 
			
		||||
type Message struct {
 | 
			
		||||
	// Deprecated information from JSONMessage.
 | 
			
		||||
	// With data only in container events.
 | 
			
		||||
	Status string `json:"status,omitempty"`
 | 
			
		||||
	ID     string `json:"id,omitempty"`
 | 
			
		||||
	From   string `json:"from,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Type   string
 | 
			
		||||
	Action string
 | 
			
		||||
	Actor  Actor
 | 
			
		||||
 | 
			
		||||
	Time     int64 `json:"time,omitempty"`
 | 
			
		||||
	TimeNano int64 `json:"timeNano,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -197,6 +197,22 @@ func (filters Args) ExactMatch(field, source string) bool {
 | 
			
		|||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FuzzyMatch returns true if the source matches exactly one of the filters,
 | 
			
		||||
// or the source has one of the filters as a prefix.
 | 
			
		||||
func (filters Args) FuzzyMatch(field, source string) bool {
 | 
			
		||||
	if filters.ExactMatch(field, source) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fieldValues := filters.fields[field]
 | 
			
		||||
	for prefix := range fieldValues {
 | 
			
		||||
		if strings.HasPrefix(source, prefix) {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Include returns true if the name of the field to filter is in the filters.
 | 
			
		||||
func (filters Args) Include(field string) bool {
 | 
			
		||||
	_, ok := filters.fields[field]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -349,3 +349,21 @@ func TestWalkValues(t *testing.T) {
 | 
			
		|||
		t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFuzzyMatch(t *testing.T) {
 | 
			
		||||
	f := NewArgs()
 | 
			
		||||
	f.Add("container", "foo")
 | 
			
		||||
 | 
			
		||||
	cases := map[string]bool{
 | 
			
		||||
		"foo":    true,
 | 
			
		||||
		"foobar": true,
 | 
			
		||||
		"barfoo": false,
 | 
			
		||||
		"bar":    false,
 | 
			
		||||
	}
 | 
			
		||||
	for source, match := range cases {
 | 
			
		||||
		got := f.FuzzyMatch("container", source)
 | 
			
		||||
		if got != match {
 | 
			
		||||
			t.Fatalf("Expected %v, got %v: %s", match, got, source)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@ import (
 | 
			
		|||
	"github.com/docker/docker/api"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	containertypes "github.com/docker/docker/api/types/container"
 | 
			
		||||
	eventtypes "github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	registrytypes "github.com/docker/docker/api/types/registry"
 | 
			
		||||
	"github.com/docker/docker/api/types/strslice"
 | 
			
		||||
| 
						 | 
				
			
			@ -47,7 +48,6 @@ import (
 | 
			
		|||
	"github.com/docker/docker/pkg/fileutils"
 | 
			
		||||
	"github.com/docker/docker/pkg/graphdb"
 | 
			
		||||
	"github.com/docker/docker/pkg/idtools"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/docker/docker/pkg/mount"
 | 
			
		||||
	"github.com/docker/docker/pkg/namesgenerator"
 | 
			
		||||
	"github.com/docker/docker/pkg/progress"
 | 
			
		||||
| 
						 | 
				
			
			@ -554,23 +554,9 @@ func (daemon *Daemon) GetByName(name string) (*container.Container, error) {
 | 
			
		|||
	return e, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getEventFilter returns a filters.Filter for a set of filters
 | 
			
		||||
func (daemon *Daemon) getEventFilter(filter filters.Args) *events.Filter {
 | 
			
		||||
	// incoming container filter can be name, id or partial id, convert to
 | 
			
		||||
	// a full container id
 | 
			
		||||
	for _, cn := range filter.Get("container") {
 | 
			
		||||
		c, err := daemon.GetContainer(cn)
 | 
			
		||||
		filter.Del("container", cn)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			filter.Add("container", c.ID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return events.NewFilter(filter, daemon.GetLabels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events.
 | 
			
		||||
func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]*jsonmessage.JSONMessage, chan interface{}) {
 | 
			
		||||
	ef := daemon.getEventFilter(filter)
 | 
			
		||||
func (daemon *Daemon) SubscribeToEvents(since, sinceNano int64, filter filters.Args) ([]eventtypes.Message, chan interface{}) {
 | 
			
		||||
	ef := events.NewFilter(filter)
 | 
			
		||||
	return daemon.EventsService.SubscribeTopic(since, sinceNano, ef)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -580,21 +566,6 @@ func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) {
 | 
			
		|||
	daemon.EventsService.Evict(listener)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabels for a container or image id
 | 
			
		||||
func (daemon *Daemon) GetLabels(id string) map[string]string {
 | 
			
		||||
	// TODO: TestCase
 | 
			
		||||
	container := daemon.containers.Get(id)
 | 
			
		||||
	if container != nil {
 | 
			
		||||
		return container.Config.Labels
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	img, err := daemon.GetImage(id)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return img.ContainerConfig.Labels
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// children returns all child containers of the container with the
 | 
			
		||||
// given name. The containers are returned as a map from the container
 | 
			
		||||
// name to a pointer to Container.
 | 
			
		||||
| 
						 | 
				
			
			@ -1032,7 +1003,8 @@ func (daemon *Daemon) TagImage(newTag reference.Named, imageName string) error {
 | 
			
		|||
	if err := daemon.referenceStore.AddTag(newTag, imageID, true); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	daemon.EventsService.Log("tag", newTag.String(), "")
 | 
			
		||||
 | 
			
		||||
	daemon.LogImageEvent(imageID.String(), newTag.String(), "tag")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1068,15 +1040,15 @@ func (daemon *Daemon) PullImage(ref reference.Named, metaHeaders map[string][]st
 | 
			
		|||
	}()
 | 
			
		||||
 | 
			
		||||
	imagePullConfig := &distribution.ImagePullConfig{
 | 
			
		||||
		MetaHeaders:     metaHeaders,
 | 
			
		||||
		AuthConfig:      authConfig,
 | 
			
		||||
		ProgressOutput:  progress.ChanOutput(progressChan),
 | 
			
		||||
		RegistryService: daemon.RegistryService,
 | 
			
		||||
		EventsService:   daemon.EventsService,
 | 
			
		||||
		MetadataStore:   daemon.distributionMetadataStore,
 | 
			
		||||
		ImageStore:      daemon.imageStore,
 | 
			
		||||
		ReferenceStore:  daemon.referenceStore,
 | 
			
		||||
		DownloadManager: daemon.downloadManager,
 | 
			
		||||
		MetaHeaders:      metaHeaders,
 | 
			
		||||
		AuthConfig:       authConfig,
 | 
			
		||||
		ProgressOutput:   progress.ChanOutput(progressChan),
 | 
			
		||||
		RegistryService:  daemon.RegistryService,
 | 
			
		||||
		ImageEventLogger: daemon.LogImageEvent,
 | 
			
		||||
		MetadataStore:    daemon.distributionMetadataStore,
 | 
			
		||||
		ImageStore:       daemon.imageStore,
 | 
			
		||||
		ReferenceStore:   daemon.referenceStore,
 | 
			
		||||
		DownloadManager:  daemon.downloadManager,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := distribution.Pull(ctx, ref, imagePullConfig)
 | 
			
		||||
| 
						 | 
				
			
			@ -1111,17 +1083,17 @@ func (daemon *Daemon) PushImage(ref reference.Named, metaHeaders map[string][]st
 | 
			
		|||
	}()
 | 
			
		||||
 | 
			
		||||
	imagePushConfig := &distribution.ImagePushConfig{
 | 
			
		||||
		MetaHeaders:     metaHeaders,
 | 
			
		||||
		AuthConfig:      authConfig,
 | 
			
		||||
		ProgressOutput:  progress.ChanOutput(progressChan),
 | 
			
		||||
		RegistryService: daemon.RegistryService,
 | 
			
		||||
		EventsService:   daemon.EventsService,
 | 
			
		||||
		MetadataStore:   daemon.distributionMetadataStore,
 | 
			
		||||
		LayerStore:      daemon.layerStore,
 | 
			
		||||
		ImageStore:      daemon.imageStore,
 | 
			
		||||
		ReferenceStore:  daemon.referenceStore,
 | 
			
		||||
		TrustKey:        daemon.trustKey,
 | 
			
		||||
		UploadManager:   daemon.uploadManager,
 | 
			
		||||
		MetaHeaders:      metaHeaders,
 | 
			
		||||
		AuthConfig:       authConfig,
 | 
			
		||||
		ProgressOutput:   progress.ChanOutput(progressChan),
 | 
			
		||||
		RegistryService:  daemon.RegistryService,
 | 
			
		||||
		ImageEventLogger: daemon.LogImageEvent,
 | 
			
		||||
		MetadataStore:    daemon.distributionMetadataStore,
 | 
			
		||||
		LayerStore:       daemon.layerStore,
 | 
			
		||||
		ImageStore:       daemon.imageStore,
 | 
			
		||||
		ReferenceStore:   daemon.referenceStore,
 | 
			
		||||
		TrustKey:         daemon.trustKey,
 | 
			
		||||
		UploadManager:    daemon.uploadManager,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := distribution.Push(ctx, ref, imagePushConfig)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,14 +1,82 @@
 | 
			
		|||
package daemon
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/libnetwork"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// LogContainerEvent generates an event related to a container.
 | 
			
		||||
func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) {
 | 
			
		||||
	daemon.EventsService.Log(
 | 
			
		||||
		action,
 | 
			
		||||
		container.ID,
 | 
			
		||||
		container.Config.Image,
 | 
			
		||||
	)
 | 
			
		||||
	attributes := copyAttributes(container.Config.Labels)
 | 
			
		||||
	if container.Config.Image != "" {
 | 
			
		||||
		attributes["image"] = container.Config.Image
 | 
			
		||||
	}
 | 
			
		||||
	attributes["name"] = strings.TrimLeft(container.Name, "/")
 | 
			
		||||
 | 
			
		||||
	actor := events.Actor{
 | 
			
		||||
		ID:         container.ID,
 | 
			
		||||
		Attributes: attributes,
 | 
			
		||||
	}
 | 
			
		||||
	daemon.EventsService.Log(action, events.ContainerEventType, actor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogImageEvent generates an event related to a container.
 | 
			
		||||
func (daemon *Daemon) LogImageEvent(imageID, refName, action string) {
 | 
			
		||||
	attributes := map[string]string{}
 | 
			
		||||
	img, err := daemon.GetImage(imageID)
 | 
			
		||||
	if err == nil && img.Config != nil {
 | 
			
		||||
		// image has not been removed yet.
 | 
			
		||||
		// it could be missing if the event is `delete`.
 | 
			
		||||
		attributes = copyAttributes(img.Config.Labels)
 | 
			
		||||
	}
 | 
			
		||||
	if refName != "" {
 | 
			
		||||
		attributes["name"] = refName
 | 
			
		||||
	}
 | 
			
		||||
	actor := events.Actor{
 | 
			
		||||
		ID:         imageID,
 | 
			
		||||
		Attributes: attributes,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.EventsService.Log(action, events.ImageEventType, actor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogVolumeEvent generates an event related to a volume.
 | 
			
		||||
func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) {
 | 
			
		||||
	actor := events.Actor{
 | 
			
		||||
		ID:         volumeID,
 | 
			
		||||
		Attributes: attributes,
 | 
			
		||||
	}
 | 
			
		||||
	daemon.EventsService.Log(action, events.VolumeEventType, actor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogNetworkEvent generates an event related to a network with only the default attributes.
 | 
			
		||||
func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) {
 | 
			
		||||
	daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LogNetworkEventWithAttributes generates an event related to a network with specific given attributes.
 | 
			
		||||
func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) {
 | 
			
		||||
	attributes["name"] = nw.Name()
 | 
			
		||||
	attributes["type"] = nw.Type()
 | 
			
		||||
 | 
			
		||||
	actor := events.Actor{
 | 
			
		||||
		ID:         nw.ID(),
 | 
			
		||||
		Attributes: attributes,
 | 
			
		||||
	}
 | 
			
		||||
	daemon.EventsService.Log(action, events.NetworkEventType, actor)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// copyAttributes guarantees that labels are not mutated by event triggers.
 | 
			
		||||
func copyAttributes(labels map[string]string) map[string]string {
 | 
			
		||||
	attributes := map[string]string{}
 | 
			
		||||
	if labels == nil {
 | 
			
		||||
		return attributes
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range labels {
 | 
			
		||||
		attributes[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	return attributes
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@ import (
 | 
			
		|||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	eventtypes "github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/pkg/pubsub"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -13,17 +13,17 @@ const (
 | 
			
		|||
	bufferSize  = 1024
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Events is pubsub channel for *jsonmessage.JSONMessage
 | 
			
		||||
// Events is pubsub channel for events generated by the engine.
 | 
			
		||||
type Events struct {
 | 
			
		||||
	mu     sync.Mutex
 | 
			
		||||
	events []*jsonmessage.JSONMessage
 | 
			
		||||
	events []eventtypes.Message
 | 
			
		||||
	pub    *pubsub.Publisher
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New returns new *Events instance
 | 
			
		||||
func New() *Events {
 | 
			
		||||
	return &Events{
 | 
			
		||||
		events: make([]*jsonmessage.JSONMessage, 0, eventsLimit),
 | 
			
		||||
		events: make([]eventtypes.Message, 0, eventsLimit),
 | 
			
		||||
		pub:    pubsub.NewPublisher(100*time.Millisecond, bufferSize),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,9 +32,9 @@ func New() *Events {
 | 
			
		|||
// last events, a channel in which you can expect new events (in form
 | 
			
		||||
// of interface{}, so you need type assertion), and a function to call
 | 
			
		||||
// to stop the stream of events.
 | 
			
		||||
func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func()) {
 | 
			
		||||
func (e *Events) Subscribe() ([]eventtypes.Message, chan interface{}, func()) {
 | 
			
		||||
	e.mu.Lock()
 | 
			
		||||
	current := make([]*jsonmessage.JSONMessage, len(e.events))
 | 
			
		||||
	current := make([]eventtypes.Message, len(e.events))
 | 
			
		||||
	copy(current, e.events)
 | 
			
		||||
	l := e.pub.Subscribe()
 | 
			
		||||
	e.mu.Unlock()
 | 
			
		||||
| 
						 | 
				
			
			@ -48,13 +48,13 @@ func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}, func
 | 
			
		|||
// SubscribeTopic adds new listener to events, returns slice of 64 stored
 | 
			
		||||
// last events, a channel in which you can expect new events (in form
 | 
			
		||||
// of interface{}, so you need type assertion).
 | 
			
		||||
func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmessage.JSONMessage, chan interface{}) {
 | 
			
		||||
func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]eventtypes.Message, chan interface{}) {
 | 
			
		||||
	e.mu.Lock()
 | 
			
		||||
	defer e.mu.Unlock()
 | 
			
		||||
 | 
			
		||||
	var buffered []*jsonmessage.JSONMessage
 | 
			
		||||
	var buffered []eventtypes.Message
 | 
			
		||||
	topic := func(m interface{}) bool {
 | 
			
		||||
		return ef.Include(m.(*jsonmessage.JSONMessage))
 | 
			
		||||
		return ef.Include(m.(eventtypes.Message))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if since != -1 {
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +64,7 @@ func (e *Events) SubscribeTopic(since, sinceNano int64, ef *Filter) ([]*jsonmess
 | 
			
		|||
				break
 | 
			
		||||
			}
 | 
			
		||||
			if ef.filter.Len() == 0 || topic(ev) {
 | 
			
		||||
				buffered = append([]*jsonmessage.JSONMessage{ev}, buffered...)
 | 
			
		||||
				buffered = append([]eventtypes.Message{ev}, buffered...)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -87,9 +87,27 @@ func (e *Events) Evict(l chan interface{}) {
 | 
			
		|||
 | 
			
		||||
// Log broadcasts event to listeners. Each listener has 100 millisecond for
 | 
			
		||||
// receiving event or it will be skipped.
 | 
			
		||||
func (e *Events) Log(action, id, from string) {
 | 
			
		||||
func (e *Events) Log(action, eventType string, actor eventtypes.Actor) {
 | 
			
		||||
	now := time.Now().UTC()
 | 
			
		||||
	jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now.Unix(), TimeNano: now.UnixNano()}
 | 
			
		||||
	jm := eventtypes.Message{
 | 
			
		||||
		Action:   action,
 | 
			
		||||
		Type:     eventType,
 | 
			
		||||
		Actor:    actor,
 | 
			
		||||
		Time:     now.Unix(),
 | 
			
		||||
		TimeNano: now.UnixNano(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// fill deprecated fields for container and images
 | 
			
		||||
	switch eventType {
 | 
			
		||||
	case eventtypes.ContainerEventType:
 | 
			
		||||
		jm.ID = actor.ID
 | 
			
		||||
		jm.Status = action
 | 
			
		||||
		jm.From = actor.Attributes["image"]
 | 
			
		||||
	case eventtypes.ImageEventType:
 | 
			
		||||
		jm.ID = actor.ID
 | 
			
		||||
		jm.Status = action
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	e.mu.Lock()
 | 
			
		||||
	if len(e.events) == cap(e.events) {
 | 
			
		||||
		// discard oldest event
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import (
 | 
			
		|||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEventsLog(t *testing.T) {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,10 +18,14 @@ func TestEventsLog(t *testing.T) {
 | 
			
		|||
	if count != 2 {
 | 
			
		||||
		t.Fatalf("Must be 2 subscribers, got %d", count)
 | 
			
		||||
	}
 | 
			
		||||
	e.Log("test", "cont", "image")
 | 
			
		||||
	actor := events.Actor{
 | 
			
		||||
		ID:         "cont",
 | 
			
		||||
		Attributes: map[string]string{"image": "image"},
 | 
			
		||||
	}
 | 
			
		||||
	e.Log("test", events.ContainerEventType, actor)
 | 
			
		||||
	select {
 | 
			
		||||
	case msg := <-l1:
 | 
			
		||||
		jmsg, ok := msg.(*jsonmessage.JSONMessage)
 | 
			
		||||
		jmsg, ok := msg.(events.Message)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			t.Fatalf("Unexpected type %T", msg)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -42,7 +46,7 @@ func TestEventsLog(t *testing.T) {
 | 
			
		|||
	}
 | 
			
		||||
	select {
 | 
			
		||||
	case msg := <-l2:
 | 
			
		||||
		jmsg, ok := msg.(*jsonmessage.JSONMessage)
 | 
			
		||||
		jmsg, ok := msg.(events.Message)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			t.Fatalf("Unexpected type %T", msg)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +74,10 @@ func TestEventsLogTimeout(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
	c := make(chan struct{})
 | 
			
		||||
	go func() {
 | 
			
		||||
		e.Log("test", "cont", "image")
 | 
			
		||||
		actor := events.Actor{
 | 
			
		||||
			ID: "image",
 | 
			
		||||
		}
 | 
			
		||||
		e.Log("test", events.ImageEventType, actor)
 | 
			
		||||
		close(c)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +95,12 @@ func TestLogEvents(t *testing.T) {
 | 
			
		|||
		action := fmt.Sprintf("action_%d", i)
 | 
			
		||||
		id := fmt.Sprintf("cont_%d", i)
 | 
			
		||||
		from := fmt.Sprintf("image_%d", i)
 | 
			
		||||
		e.Log(action, id, from)
 | 
			
		||||
 | 
			
		||||
		actor := events.Actor{
 | 
			
		||||
			ID:         id,
 | 
			
		||||
			Attributes: map[string]string{"image": from},
 | 
			
		||||
		}
 | 
			
		||||
		e.Log(action, events.ContainerEventType, actor)
 | 
			
		||||
	}
 | 
			
		||||
	time.Sleep(50 * time.Millisecond)
 | 
			
		||||
	current, l, _ := e.Subscribe()
 | 
			
		||||
| 
						 | 
				
			
			@ -97,16 +109,21 @@ func TestLogEvents(t *testing.T) {
 | 
			
		|||
		action := fmt.Sprintf("action_%d", num)
 | 
			
		||||
		id := fmt.Sprintf("cont_%d", num)
 | 
			
		||||
		from := fmt.Sprintf("image_%d", num)
 | 
			
		||||
		e.Log(action, id, from)
 | 
			
		||||
 | 
			
		||||
		actor := events.Actor{
 | 
			
		||||
			ID:         id,
 | 
			
		||||
			Attributes: map[string]string{"image": from},
 | 
			
		||||
		}
 | 
			
		||||
		e.Log(action, events.ContainerEventType, actor)
 | 
			
		||||
	}
 | 
			
		||||
	if len(e.events) != eventsLimit {
 | 
			
		||||
		t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var msgs []*jsonmessage.JSONMessage
 | 
			
		||||
	var msgs []events.Message
 | 
			
		||||
	for len(msgs) < 10 {
 | 
			
		||||
		m := <-l
 | 
			
		||||
		jm, ok := (m).(*jsonmessage.JSONMessage)
 | 
			
		||||
		jm, ok := (m).(events.Message)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			t.Fatalf("Unexpected type %T", m)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,46 +1,58 @@
 | 
			
		|||
package events
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/docker/docker/api/types/events"
 | 
			
		||||
	"github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/docker/docker/reference"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Filter can filter out docker events from a stream
 | 
			
		||||
type Filter struct {
 | 
			
		||||
	filter    filters.Args
 | 
			
		||||
	getLabels func(id string) map[string]string
 | 
			
		||||
	filter filters.Args
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFilter creates a new Filter
 | 
			
		||||
func NewFilter(filter filters.Args, getLabels func(id string) map[string]string) *Filter {
 | 
			
		||||
	return &Filter{filter: filter, getLabels: getLabels}
 | 
			
		||||
func NewFilter(filter filters.Args) *Filter {
 | 
			
		||||
	return &Filter{filter: filter}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Include returns true when the event ev is included by the filters
 | 
			
		||||
func (ef *Filter) Include(ev *jsonmessage.JSONMessage) bool {
 | 
			
		||||
	return ef.filter.ExactMatch("event", ev.Status) &&
 | 
			
		||||
		ef.filter.ExactMatch("container", ev.ID) &&
 | 
			
		||||
		ef.isImageIncluded(ev.ID, ev.From) &&
 | 
			
		||||
		ef.isLabelFieldIncluded(ev.ID)
 | 
			
		||||
func (ef *Filter) Include(ev events.Message) bool {
 | 
			
		||||
	if ev.Type != events.ContainerEventType && ev.Type != events.ImageEventType {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return ef.filter.ExactMatch("event", ev.Action) &&
 | 
			
		||||
		ef.matchContainer(ev) &&
 | 
			
		||||
		ef.isImageIncluded(ev) &&
 | 
			
		||||
		ef.isLabelFieldIncluded(ev.Actor.Attributes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ef *Filter) isLabelFieldIncluded(id string) bool {
 | 
			
		||||
func (ef *Filter) isLabelFieldIncluded(attributes map[string]string) bool {
 | 
			
		||||
	if !ef.filter.Include("label") {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return ef.filter.MatchKVList("label", ef.getLabels(id))
 | 
			
		||||
	return ef.filter.MatchKVList("label", attributes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ef *Filter) matchContainer(ev events.Message) bool {
 | 
			
		||||
	return ef.filter.FuzzyMatch("container", ev.Actor.ID) ||
 | 
			
		||||
		ef.filter.FuzzyMatch("container", ev.Actor.Attributes["name"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The image filter will be matched against both event.ID (for image events)
 | 
			
		||||
// and event.From (for container events), so that any container that was created
 | 
			
		||||
// from an image will be included in the image events. Also compare both
 | 
			
		||||
// against the stripped repo name without any tags.
 | 
			
		||||
func (ef *Filter) isImageIncluded(eventID string, eventFrom string) bool {
 | 
			
		||||
	return ef.filter.ExactMatch("image", eventID) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", eventFrom) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", stripTag(eventID)) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", stripTag(eventFrom))
 | 
			
		||||
func (ef *Filter) isImageIncluded(ev events.Message) bool {
 | 
			
		||||
	id := ev.ID
 | 
			
		||||
	var imageName string
 | 
			
		||||
	if n, ok := ev.Actor.Attributes["image"]; ok {
 | 
			
		||||
		imageName = n
 | 
			
		||||
	}
 | 
			
		||||
	return ef.filter.ExactMatch("image", id) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", imageName) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", stripTag(id)) ||
 | 
			
		||||
		ef.filter.ExactMatch("image", stripTag(imageName))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func stripTag(image string) string {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										36
									
								
								daemon/events_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								daemon/events_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,36 @@
 | 
			
		|||
package daemon
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	containertypes "github.com/docker/docker/api/types/container"
 | 
			
		||||
	"github.com/docker/docker/container"
 | 
			
		||||
	"github.com/docker/docker/daemon/events"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestLogContainerCopyLabels(t *testing.T) {
 | 
			
		||||
	e := events.New()
 | 
			
		||||
	_, l, _ := e.Subscribe()
 | 
			
		||||
	defer e.Evict(l)
 | 
			
		||||
 | 
			
		||||
	container := &container.Container{
 | 
			
		||||
		CommonContainer: container.CommonContainer{
 | 
			
		||||
			ID:   "container_id",
 | 
			
		||||
			Name: "container_name",
 | 
			
		||||
			Config: &containertypes.Config{
 | 
			
		||||
				Labels: map[string]string{
 | 
			
		||||
					"node": "1",
 | 
			
		||||
					"os":   "alpine",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	daemon := &Daemon{
 | 
			
		||||
		EventsService: e,
 | 
			
		||||
	}
 | 
			
		||||
	daemon.LogContainerEvent(container, "create")
 | 
			
		||||
 | 
			
		||||
	if _, mutated := container.Config.Labels["image"]; mutated {
 | 
			
		||||
		t.Fatalf("Expected to not mutate the container labels, got %q", container.Config.Labels)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +87,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 | 
			
		|||
 | 
			
		||||
		untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
 | 
			
		||||
 | 
			
		||||
		daemon.EventsService.Log("untag", imgID.String(), "")
 | 
			
		||||
		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
 | 
			
		||||
		records = append(records, untaggedRecord)
 | 
			
		||||
 | 
			
		||||
		// If has remaining references then untag finishes the remove
 | 
			
		||||
| 
						 | 
				
			
			@ -109,7 +109,7 @@ func (daemon *Daemon) ImageDelete(imageRef string, force, prune bool) ([]types.I
 | 
			
		|||
 | 
			
		||||
			untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
 | 
			
		||||
 | 
			
		||||
			daemon.EventsService.Log("untag", imgID.String(), "")
 | 
			
		||||
			daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
 | 
			
		||||
			records = append(records, untaggedRecord)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +174,7 @@ func (daemon *Daemon) removeAllReferencesToImageID(imgID image.ID, records *[]ty
 | 
			
		|||
 | 
			
		||||
		untaggedRecord := types.ImageDelete{Untagged: parsedRef.String()}
 | 
			
		||||
 | 
			
		||||
		daemon.EventsService.Log("untag", imgID.String(), "")
 | 
			
		||||
		daemon.LogImageEvent(imgID.String(), imgID.String(), "untag")
 | 
			
		||||
		*records = append(*records, untaggedRecord)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -243,7 +243,7 @@ func (daemon *Daemon) imageDeleteHelper(imgID image.ID, records *[]types.ImageDe
 | 
			
		|||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.EventsService.Log("delete", imgID.String(), "")
 | 
			
		||||
	daemon.LogImageEvent(imgID.String(), imgID.String(), "delete")
 | 
			
		||||
	*records = append(*records, types.ImageDelete{Deleted: imgID.String()})
 | 
			
		||||
	for _, removedLayer := range removedLayers {
 | 
			
		||||
		*records = append(*records, types.ImageDelete{Deleted: removedLayer.ChainID.String()})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -97,7 +97,7 @@ func (daemon *Daemon) ImportImage(src string, newRef reference.Named, msg string
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.EventsService.Log("import", id.String(), "")
 | 
			
		||||
	daemon.LogImageEvent(id.String(), id.String(), "import")
 | 
			
		||||
	outStream.Write(sf.FormatStatus("", id.String()))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,5 +54,7 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	daemon.LogContainerEvent(container, "update")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import (
 | 
			
		|||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/daemon/events"
 | 
			
		||||
	"github.com/docker/docker/distribution/metadata"
 | 
			
		||||
	"github.com/docker/docker/distribution/xfer"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
| 
						 | 
				
			
			@ -31,8 +30,8 @@ type ImagePullConfig struct {
 | 
			
		|||
	// RegistryService is the registry service to use for TLS configuration
 | 
			
		||||
	// and endpoint lookup.
 | 
			
		||||
	RegistryService *registry.Service
 | 
			
		||||
	// EventsService is the events service to use for logging.
 | 
			
		||||
	EventsService *events.Events
 | 
			
		||||
	// ImageEventLogger notifies events for a given image
 | 
			
		||||
	ImageEventLogger func(id, name, action string)
 | 
			
		||||
	// MetadataStore is the storage backend for distribution-specific
 | 
			
		||||
	// metadata.
 | 
			
		||||
	MetadataStore metadata.Store
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +159,7 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		imagePullConfig.EventsService.Log("pull", ref.String(), "")
 | 
			
		||||
		imagePullConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "pull")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,6 @@ import (
 | 
			
		|||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/docker/distribution/digest"
 | 
			
		||||
	"github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/daemon/events"
 | 
			
		||||
	"github.com/docker/docker/distribution/metadata"
 | 
			
		||||
	"github.com/docker/docker/distribution/xfer"
 | 
			
		||||
	"github.com/docker/docker/image"
 | 
			
		||||
| 
						 | 
				
			
			@ -35,8 +34,8 @@ type ImagePushConfig struct {
 | 
			
		|||
	// RegistryService is the registry service to use for TLS configuration
 | 
			
		||||
	// and endpoint lookup.
 | 
			
		||||
	RegistryService *registry.Service
 | 
			
		||||
	// EventsService is the events service to use for logging.
 | 
			
		||||
	EventsService *events.Events
 | 
			
		||||
	// ImageEventLogger notifies events for a given image
 | 
			
		||||
	ImageEventLogger func(id, name, action string)
 | 
			
		||||
	// MetadataStore is the storage backend for distribution-specific
 | 
			
		||||
	// metadata.
 | 
			
		||||
	MetadataStore metadata.Store
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +155,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 | 
			
		|||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		imagePushConfig.EventsService.Log("push", repoInfo.Name(), "")
 | 
			
		||||
		imagePushConfig.ImageEventLogger(ref.String(), repoInfo.Name(), "push")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,16 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/integration/checker"
 | 
			
		||||
	"github.com/docker/docker/pkg/jsonmessage"
 | 
			
		||||
	"github.com/go-check/check"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -28,3 +34,40 @@ func (s *DockerSuite) TestEventsApiEmptyOutput(c *check.C) {
 | 
			
		|||
		c.Fatal("timeout waiting for events api to respond, should have responded immediately")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsApiBackwardsCompatible(c *check.C) {
 | 
			
		||||
	since := daemonTime(c).Unix()
 | 
			
		||||
	ts := strconv.FormatInt(since, 10)
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
 | 
			
		||||
	containerID := strings.TrimSpace(out)
 | 
			
		||||
	c.Assert(waitRun(containerID), checker.IsNil)
 | 
			
		||||
 | 
			
		||||
	q := url.Values{}
 | 
			
		||||
	q.Set("since", ts)
 | 
			
		||||
 | 
			
		||||
	_, body, err := sockRequestRaw("GET", "/events?"+q.Encode(), nil, "")
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	defer body.Close()
 | 
			
		||||
 | 
			
		||||
	dec := json.NewDecoder(body)
 | 
			
		||||
	var containerCreateEvent *jsonmessage.JSONMessage
 | 
			
		||||
	for {
 | 
			
		||||
		var event jsonmessage.JSONMessage
 | 
			
		||||
		if err := dec.Decode(&event); err != nil {
 | 
			
		||||
			if err == io.EOF {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			c.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
		if event.Status == "create" && event.ID == containerID {
 | 
			
		||||
			containerCreateEvent = &event
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Assert(containerCreateEvent, checker.Not(checker.IsNil))
 | 
			
		||||
	c.Assert(containerCreateEvent.Status, checker.Equals, "create")
 | 
			
		||||
	c.Assert(containerCreateEvent.ID, checker.Equals, containerID)
 | 
			
		||||
	c.Assert(containerCreateEvent.From, checker.Equals, "busybox")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1874,6 +1874,12 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
 | 
			
		|||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
	name := "testbuildcancellation"
 | 
			
		||||
 | 
			
		||||
	observer, err := newEventObserver(c)
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	err = observer.Start()
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	defer observer.Stop()
 | 
			
		||||
 | 
			
		||||
	// (Note: one year, will never finish)
 | 
			
		||||
	ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -1881,15 +1887,6 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
 | 
			
		|||
	}
 | 
			
		||||
	defer ctx.Close()
 | 
			
		||||
 | 
			
		||||
	eventStart := make(chan struct{})
 | 
			
		||||
	eventDie := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	observer, err := newEventObserver(c)
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	err = observer.Start()
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	defer observer.Stop()
 | 
			
		||||
 | 
			
		||||
	buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
 | 
			
		||||
	buildCmd.Dir = ctx.Dir
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1917,26 +1914,18 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
 | 
			
		|||
		c.Fatalf("Unable to find build container id in build output:\n%s", outputBuffer.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	matchStart := regexp.MustCompile(buildID + `.* start\z`)
 | 
			
		||||
	matchDie := regexp.MustCompile(buildID + `.* die\z`)
 | 
			
		||||
 | 
			
		||||
	matcher := func(text string) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case matchStart.MatchString(text):
 | 
			
		||||
			close(eventStart)
 | 
			
		||||
		case matchDie.MatchString(text):
 | 
			
		||||
			close(eventDie)
 | 
			
		||||
		}
 | 
			
		||||
	testActions := map[string]chan bool{
 | 
			
		||||
		"start": make(chan bool),
 | 
			
		||||
		"die":   make(chan bool),
 | 
			
		||||
	}
 | 
			
		||||
	go observer.Match(matcher)
 | 
			
		||||
 | 
			
		||||
	go observer.Match(matchEventLine(buildID, "container", testActions))
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(10 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(buildID, "start"))
 | 
			
		||||
	case <-eventStart:
 | 
			
		||||
		// Proceeds from here when we see the container fly past in the
 | 
			
		||||
		// output of "docker events".
 | 
			
		||||
		// Now we know the container is running.
 | 
			
		||||
	case <-testActions["start"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Send a kill to the `docker build` command.
 | 
			
		||||
| 
						 | 
				
			
			@ -1952,13 +1941,10 @@ func (s *DockerSuite) TestBuildCancellationKillsSleep(c *check.C) {
 | 
			
		|||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(10 * time.Second):
 | 
			
		||||
		// If we don't get here in a timely fashion, it wasn't killed.
 | 
			
		||||
		c.Fatal(observer.TimeoutError(buildID, "die"))
 | 
			
		||||
	case <-eventDie:
 | 
			
		||||
		// We saw the container shut down in the `docker events` stream,
 | 
			
		||||
		// as expected.
 | 
			
		||||
	case <-testActions["die"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestBuildRm(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -6489,33 +6475,26 @@ func (s *DockerSuite) TestBuildNoNamedVolume(c *check.C) {
 | 
			
		|||
func (s *DockerSuite) TestBuildTagEvent(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
 | 
			
		||||
	observer, err := newEventObserver(c, "--filter", "event=tag")
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	err = observer.Start()
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
	defer observer.Stop()
 | 
			
		||||
	since := daemonTime(c).Unix()
 | 
			
		||||
 | 
			
		||||
	dockerFile := `FROM busybox
 | 
			
		||||
	RUN echo events
 | 
			
		||||
	`
 | 
			
		||||
	_, err = buildImage("test", dockerFile, false)
 | 
			
		||||
	_, err := buildImage("test", dockerFile, false)
 | 
			
		||||
	c.Assert(err, check.IsNil)
 | 
			
		||||
 | 
			
		||||
	matchTag := regexp.MustCompile("test:latest")
 | 
			
		||||
	eventTag := make(chan bool)
 | 
			
		||||
	matcher := func(text string) {
 | 
			
		||||
		if matchTag.MatchString(text) {
 | 
			
		||||
			close(eventTag)
 | 
			
		||||
	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "type=image")
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	actions := eventActionsByIDAndType(c, events, "test:latest", "image")
 | 
			
		||||
	var foundTag bool
 | 
			
		||||
	for _, a := range actions {
 | 
			
		||||
		if a == "tag" {
 | 
			
		||||
			foundTag = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	go observer.Match(matcher)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(10 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError("test:latest", "tag"))
 | 
			
		||||
	case <-eventTag:
 | 
			
		||||
		// We saw the tag event as expected.
 | 
			
		||||
	}
 | 
			
		||||
	c.Assert(foundTag, checker.True, check.Commentf("No tag event found:\n%s", out))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// #15780
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,6 @@ import (
 | 
			
		|||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +66,6 @@ func (s *DockerSuite) TestEventsUntag(c *check.C) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "images", "-q")
 | 
			
		||||
	image := strings.Split(out, "\n")[0]
 | 
			
		||||
	_, _, err := dockerCmdWithError("run", "--name", "testeventdie", image, "blerg")
 | 
			
		||||
| 
						 | 
				
			
			@ -75,14 +73,12 @@ func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) {
 | 
			
		|||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
	c.Assert(len(events), checker.GreaterThan, 1) //Missing expected event
 | 
			
		||||
 | 
			
		||||
	startEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
	dieEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
 | 
			
		||||
	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
 | 
			
		||||
	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
 | 
			
		||||
	nEvents := len(events)
 | 
			
		||||
	c.Assert(nEvents, checker.GreaterOrEqualThan, 1) //Missing expected event
 | 
			
		||||
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start")
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "die")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsLimit(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -114,65 +110,83 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) {
 | 
			
		|||
 | 
			
		||||
func (s *DockerSuite) TestEventsContainerEvents(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
	dockerCmd(c, "run", "--rm", "busybox", "true")
 | 
			
		||||
	containerID, _ := dockerCmd(c, "run", "--rm", "--name", "container-events-test", "busybox", "true")
 | 
			
		||||
	containerID = strings.TrimSpace(containerID)
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
	events = events[:len(events)-1]
 | 
			
		||||
	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	createEvent := strings.Fields(events[len(events)-5])
 | 
			
		||||
	attachEvent := strings.Fields(events[len(events)-4])
 | 
			
		||||
	startEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
	dieEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
	destroyEvent := strings.Fields(events[len(events)-1])
 | 
			
		||||
	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
 | 
			
		||||
	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
 | 
			
		||||
	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
 | 
			
		||||
	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
 | 
			
		||||
	c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
 | 
			
		||||
 | 
			
		||||
	nEvents := len(events)
 | 
			
		||||
	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	containerEvents := eventActionsByIDAndType(c, events, "container-events-test", "container")
 | 
			
		||||
	c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
 | 
			
		||||
 | 
			
		||||
	c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
	dockerCmd(c, "run", "--rm", "busybox", "true")
 | 
			
		||||
	dockerCmd(c, "run", "--rm", "--name", "since-epoch-test", "busybox", "true")
 | 
			
		||||
	timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano)
 | 
			
		||||
	timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1)
 | 
			
		||||
	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning),
 | 
			
		||||
		fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since='%s'", timeBeginning), fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
	events = events[:len(events)-1]
 | 
			
		||||
	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	createEvent := strings.Fields(events[len(events)-5])
 | 
			
		||||
	attachEvent := strings.Fields(events[len(events)-4])
 | 
			
		||||
	startEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
	dieEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
	destroyEvent := strings.Fields(events[len(events)-1])
 | 
			
		||||
	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
 | 
			
		||||
	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
 | 
			
		||||
	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
 | 
			
		||||
	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
 | 
			
		||||
	c.Assert(destroyEvent[len(destroyEvent)-1], checker.Equals, "destroy", check.Commentf("event should be destroy, not %#v", destroyEvent))
 | 
			
		||||
 | 
			
		||||
	nEvents := len(events)
 | 
			
		||||
	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	containerEvents := eventActionsByIDAndType(c, events, "since-epoch-test", "container")
 | 
			
		||||
	c.Assert(containerEvents, checker.HasLen, 5, check.Commentf("events: %v", events))
 | 
			
		||||
 | 
			
		||||
	c.Assert(containerEvents[0], checker.Equals, "create", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[1], checker.Equals, "attach", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[2], checker.Equals, "start", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[3], checker.Equals, "die", check.Commentf(out))
 | 
			
		||||
	c.Assert(containerEvents[4], checker.Equals, "destroy", check.Commentf(out))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
 | 
			
		||||
	observer, err := newEventObserver(c)
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	err = observer.Start()
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	defer observer.Stop()
 | 
			
		||||
 | 
			
		||||
	name := "testimageevents"
 | 
			
		||||
	_, err := buildImage(name,
 | 
			
		||||
	imageID, err := buildImage(name,
 | 
			
		||||
		`FROM scratch
 | 
			
		||||
		MAINTAINER "docker"`,
 | 
			
		||||
		true)
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	c.Assert(deleteImages(name), checker.IsNil)
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
 | 
			
		||||
	events = events[:len(events)-1]
 | 
			
		||||
	c.Assert(len(events), checker.GreaterOrEqualThan, 2) //Missing expected event
 | 
			
		||||
	untagEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
	deleteEvent := strings.Fields(events[len(events)-1])
 | 
			
		||||
	c.Assert(untagEvent[len(untagEvent)-1], checker.Equals, "untag", check.Commentf("untag should be untag, not %#v", untagEvent))
 | 
			
		||||
	c.Assert(deleteEvent[len(deleteEvent)-1], checker.Equals, "delete", check.Commentf("untag should be delete, not %#v", untagEvent))
 | 
			
		||||
	testActions := map[string]chan bool{
 | 
			
		||||
		"untag":  make(chan bool),
 | 
			
		||||
		"delete": make(chan bool),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go observer.Match(matchEventLine(imageID, "image", testActions))
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(10 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(imageID, "untag"))
 | 
			
		||||
	case <-testActions["untag"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(10 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(imageID, "delete"))
 | 
			
		||||
	case <-testActions["delete"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsImageTag(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -189,10 +203,11 @@ func (s *DockerSuite) TestEventsImageTag(c *check.C) {
 | 
			
		|||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	c.Assert(events, checker.HasLen, 1, check.Commentf("was expecting 1 event. out=%s", out))
 | 
			
		||||
	event := strings.TrimSpace(events[0])
 | 
			
		||||
	expectedStr := image + ": tag"
 | 
			
		||||
 | 
			
		||||
	c.Assert(event, checker.HasSuffix, expectedStr, check.Commentf("wrong event format. expected='%s' got=%s", expectedStr, event))
 | 
			
		||||
 | 
			
		||||
	matches := parseEventText(event)
 | 
			
		||||
	c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
	c.Assert(matchEventID(matches, image), checker.True, check.Commentf("matches: %v\nout:\n%s", matches, out))
 | 
			
		||||
	c.Assert(matches["action"], checker.Equals, "tag")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsImagePull(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,68 +223,46 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) {
 | 
			
		|||
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	event := strings.TrimSpace(events[len(events)-1])
 | 
			
		||||
 | 
			
		||||
	c.Assert(event, checker.HasSuffix, "hello-world:latest: pull", check.Commentf("Missing pull event - got:%q", event))
 | 
			
		||||
	matches := parseEventText(event)
 | 
			
		||||
	c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
	c.Assert(matches["id"], checker.Equals, "hello-world:latest")
 | 
			
		||||
	c.Assert(matches["action"], checker.Equals, "pull")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsImageImport(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
 | 
			
		||||
	observer, err := newEventObserver(c)
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
 | 
			
		||||
	err = observer.Start()
 | 
			
		||||
	c.Assert(err, checker.IsNil)
 | 
			
		||||
	defer observer.Stop()
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "run", "-d", "busybox", "true")
 | 
			
		||||
	cleanedContainerID := strings.TrimSpace(out)
 | 
			
		||||
 | 
			
		||||
	out, _, err = runCommandPipelineWithOutput(
 | 
			
		||||
	since := daemonTime(c).Unix()
 | 
			
		||||
	out, _, err := runCommandPipelineWithOutput(
 | 
			
		||||
		exec.Command(dockerBinary, "export", cleanedContainerID),
 | 
			
		||||
		exec.Command(dockerBinary, "import", "-"),
 | 
			
		||||
	)
 | 
			
		||||
	c.Assert(err, checker.IsNil, check.Commentf("import failed with output: %q", out))
 | 
			
		||||
	imageRef := strings.TrimSpace(out)
 | 
			
		||||
 | 
			
		||||
	eventImport := make(chan bool)
 | 
			
		||||
	matchImport := regexp.MustCompile(imageRef + `: import\z`)
 | 
			
		||||
	matcher := func(text string) {
 | 
			
		||||
		if matchImport.MatchString(text) {
 | 
			
		||||
			close(eventImport)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	go observer.Match(matcher)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(5 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(imageRef, "import"))
 | 
			
		||||
	case <-eventImport:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
	out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=import")
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	c.Assert(events, checker.HasLen, 1)
 | 
			
		||||
	matches := parseEventText(events[0])
 | 
			
		||||
	c.Assert(matches["id"], checker.Equals, imageRef, check.Commentf("matches: %v\nout:\n%s\n", matches, out))
 | 
			
		||||
	c.Assert(matches["action"], checker.Equals, "import", check.Commentf("matches: %v\nout:\n%s\n", matches, out))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsFilters(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
	parseEvents := func(out, match string) {
 | 
			
		||||
		events := strings.Split(out, "\n")
 | 
			
		||||
		events = events[:len(events)-1]
 | 
			
		||||
		for _, event := range events {
 | 
			
		||||
			eventFields := strings.Fields(event)
 | 
			
		||||
			eventName := eventFields[len(eventFields)-1]
 | 
			
		||||
			c.Assert(eventName, checker.Matches, match)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	since := daemonTime(c).Unix()
 | 
			
		||||
	dockerCmd(c, "run", "--rm", "busybox", "true")
 | 
			
		||||
	dockerCmd(c, "run", "--rm", "busybox", "true")
 | 
			
		||||
	out, _ := dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")
 | 
			
		||||
	parseEvents(out, "die")
 | 
			
		||||
	parseEvents(c, out, "die")
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")
 | 
			
		||||
	parseEvents(out, "((die)|(start))")
 | 
			
		||||
	parseEvents(c, out, "die|start")
 | 
			
		||||
 | 
			
		||||
	// make sure we at least got 2 start events
 | 
			
		||||
	count := strings.Count(out, "start")
 | 
			
		||||
| 
						 | 
				
			
			@ -385,15 +378,9 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
 | 
			
		|||
			return fmt.Errorf("expected 4 events, got %v", events)
 | 
			
		||||
		}
 | 
			
		||||
		for _, event := range events {
 | 
			
		||||
			e := strings.Fields(event)
 | 
			
		||||
			if len(e) < 3 {
 | 
			
		||||
				return fmt.Errorf("got malformed event: %s", event)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Check the id
 | 
			
		||||
			parsedID := strings.TrimSuffix(e[1], ":")
 | 
			
		||||
			if parsedID != id {
 | 
			
		||||
				return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID)
 | 
			
		||||
			matches := parseEventText(event)
 | 
			
		||||
			if !matchEventID(matches, id) {
 | 
			
		||||
				return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, matches["id"])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -450,21 +437,21 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) {
 | 
			
		|||
	select {
 | 
			
		||||
	case <-time.After(5 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(containerID, "create"))
 | 
			
		||||
	case <-eventCreate:
 | 
			
		||||
	case <-testActions["create"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(5 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(containerID, "start"))
 | 
			
		||||
	case <-eventStart:
 | 
			
		||||
	case <-testActions["start"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case <-time.After(5 * time.Second):
 | 
			
		||||
		c.Fatal(observer.TimeoutError(containerID, "die"))
 | 
			
		||||
	case <-eventDie:
 | 
			
		||||
	case <-testActions["die"]:
 | 
			
		||||
		// ignore, done
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -490,7 +477,7 @@ func (s *DockerSuite) TestEventsCommit(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "stop", cID)
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " commit\n", check.Commentf("Missing 'commit' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "commit", check.Commentf("Missing 'commit' log event"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsCopy(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -515,12 +502,12 @@ func (s *DockerSuite) TestEventsCopy(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "cp", "cptest:/tmp/file", tempFile.Name())
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " archive-path\n", check.Commentf("Missing 'archive-path' log event\n"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "archive-path", check.Commentf("Missing 'archive-path' log event\n"))
 | 
			
		||||
 | 
			
		||||
	dockerCmd(c, "cp", tempFile.Name(), "cptest:/tmp/filecopy")
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container=cptest", "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " extract-to-dir\n", check.Commentf("Missing 'extract-to-dir' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "extract-to-dir", check.Commentf("Missing 'extract-to-dir' log event"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsResize(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -539,7 +526,7 @@ func (s *DockerSuite) TestEventsResize(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "stop", cID)
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " resize\n", check.Commentf("Missing 'resize' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "resize", check.Commentf("Missing 'resize' log event"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsAttach(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -571,7 +558,7 @@ func (s *DockerSuite) TestEventsAttach(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "stop", cID)
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " attach\n", check.Commentf("Missing 'attach' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "attach", check.Commentf("Missing 'attach' log event"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsRename(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -582,7 +569,7 @@ func (s *DockerSuite) TestEventsRename(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "rename", "oldName", "newName")
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=newName", "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " rename\n", check.Commentf("Missing 'rename' log event\n"))
 | 
			
		||||
	c.Assert(out, checker.Contains, "rename", check.Commentf("Missing 'rename' log event\n"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsTop(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -597,7 +584,7 @@ func (s *DockerSuite) TestEventsTop(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "stop", cID)
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "container="+cID, "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, " top\n", check.Commentf("Missing 'top' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, " top", check.Commentf("Missing 'top' log event"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// #13753
 | 
			
		||||
| 
						 | 
				
			
			@ -624,5 +611,5 @@ func (s *DockerRegistrySuite) TestEventsImageFilterPush(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "push", repoName)
 | 
			
		||||
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "--since=0", "-f", "image="+repoName, "-f", "event=push", "--until="+strconv.Itoa(int(since)))
 | 
			
		||||
	c.Assert(out, checker.Contains, repoName+": push\n", check.Commentf("Missing 'push' log event"))
 | 
			
		||||
	c.Assert(out, checker.Contains, repoName, check.Commentf("Missing 'push' log event for %s", repoName))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,18 +65,14 @@ func (s *DockerSuite) TestEventsOOMDisableFalse(c *check.C) {
 | 
			
		|||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomFalse", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
 | 
			
		||||
	c.Assert(len(events), checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	nEvents := len(events)
 | 
			
		||||
 | 
			
		||||
	createEvent := strings.Fields(events[len(events)-5])
 | 
			
		||||
	attachEvent := strings.Fields(events[len(events)-4])
 | 
			
		||||
	startEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
	oomEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
	dieEvent := strings.Fields(events[len(events)-1])
 | 
			
		||||
	c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
 | 
			
		||||
	c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
 | 
			
		||||
	c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
 | 
			
		||||
	c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
 | 
			
		||||
	c.Assert(dieEvent[len(dieEvent)-1], checker.Equals, "die", check.Commentf("event should be die, not %#v", dieEvent))
 | 
			
		||||
	c.Assert(nEvents, checker.GreaterOrEqualThan, 5) //Missing expected event
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-5]), checker.Equals, "create")
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "attach")
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "start")
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "oom")
 | 
			
		||||
	c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "die")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -98,17 +94,13 @@ func (s *DockerSuite) TestEventsOOMDisableTrue(c *check.C) {
 | 
			
		|||
 | 
			
		||||
		out, _ := dockerCmd(c, "events", "--since=0", "-f", "container=oomTrue", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
		events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
 | 
			
		||||
		c.Assert(len(events), checker.GreaterOrEqualThan, 4) //Missing expected event
 | 
			
		||||
		nEvents := len(events)
 | 
			
		||||
		c.Assert(nEvents, checker.GreaterOrEqualThan, 4) //Missing expected event
 | 
			
		||||
 | 
			
		||||
		createEvent := strings.Fields(events[len(events)-4])
 | 
			
		||||
		attachEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
		startEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
		oomEvent := strings.Fields(events[len(events)-1])
 | 
			
		||||
 | 
			
		||||
		c.Assert(createEvent[len(createEvent)-1], checker.Equals, "create", check.Commentf("event should be create, not %#v", createEvent))
 | 
			
		||||
		c.Assert(attachEvent[len(attachEvent)-1], checker.Equals, "attach", check.Commentf("event should be attach, not %#v", attachEvent))
 | 
			
		||||
		c.Assert(startEvent[len(startEvent)-1], checker.Equals, "start", check.Commentf("event should be start, not %#v", startEvent))
 | 
			
		||||
		c.Assert(oomEvent[len(oomEvent)-1], checker.Equals, "oom", check.Commentf("event should be oom, not %#v", oomEvent))
 | 
			
		||||
		c.Assert(parseEventAction(c, events[nEvents-4]), checker.Equals, "create")
 | 
			
		||||
		c.Assert(parseEventAction(c, events[nEvents-3]), checker.Equals, "attach")
 | 
			
		||||
		c.Assert(parseEventAction(c, events[nEvents-2]), checker.Equals, "start")
 | 
			
		||||
		c.Assert(parseEventAction(c, events[nEvents-1]), checker.Equals, "oom")
 | 
			
		||||
 | 
			
		||||
		out, _ = dockerCmd(c, "inspect", "-f", "{{.State.Status}}", "oomTrue")
 | 
			
		||||
		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) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// #18453
 | 
			
		||||
func (s *DockerSuite) TestEventsContainerFilter(c *check.C) {
 | 
			
		||||
func (s *DockerSuite) TestEventsContainerFilterByName(c *check.C) {
 | 
			
		||||
	testRequires(c, DaemonIsLinux)
 | 
			
		||||
	out, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
 | 
			
		||||
	c1 := strings.TrimSpace(out)
 | 
			
		||||
	waitRun(c1)
 | 
			
		||||
	out, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top")
 | 
			
		||||
	c2 := strings.TrimSpace(out)
 | 
			
		||||
	waitRun(c2)
 | 
			
		||||
	out, _ = dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	c.Assert(out, checker.Contains, c1, check.Commentf("Missing event of container (foo)"))
 | 
			
		||||
	c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf("Should not contain event of container (bar)"))
 | 
			
		||||
	cOut, _ := dockerCmd(c, "run", "--name=foo", "-d", "busybox", "top")
 | 
			
		||||
	c1 := strings.TrimSpace(cOut)
 | 
			
		||||
	waitRun("foo")
 | 
			
		||||
	cOut, _ = dockerCmd(c, "run", "--name=bar", "-d", "busybox", "top")
 | 
			
		||||
	c2 := strings.TrimSpace(cOut)
 | 
			
		||||
	waitRun("bar")
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "-f", "container=foo", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	c.Assert(out, checker.Contains, c1, check.Commentf(out))
 | 
			
		||||
	c.Assert(out, checker.Not(checker.Contains), c2, check.Commentf(out))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// #18453
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,15 +23,11 @@ func (s *DockerSuite) TestPause(c *check.C) {
 | 
			
		|||
	dockerCmd(c, "unpause", name)
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
	c.Assert(len(events) > 1, checker.Equals, true)
 | 
			
		||||
 | 
			
		||||
	pauseEvent := strings.Fields(events[len(events)-3])
 | 
			
		||||
	unpauseEvent := strings.Fields(events[len(events)-2])
 | 
			
		||||
 | 
			
		||||
	c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
 | 
			
		||||
	c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	actions := eventActionsByIDAndType(c, events, name, "container")
 | 
			
		||||
 | 
			
		||||
	c.Assert(actions[len(actions)-2], checker.Equals, "pause")
 | 
			
		||||
	c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
 | 
			
		||||
| 
						 | 
				
			
			@ -53,21 +49,12 @@ func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) {
 | 
			
		|||
	dockerCmd(c, append([]string{"unpause"}, containers...)...)
 | 
			
		||||
 | 
			
		||||
	out, _ := dockerCmd(c, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
 | 
			
		||||
	events := strings.Split(out, "\n")
 | 
			
		||||
	c.Assert(len(events) > len(containers)*3-2, checker.Equals, true)
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
 | 
			
		||||
	pauseEvents := make([][]string, len(containers))
 | 
			
		||||
	unpauseEvents := make([][]string, len(containers))
 | 
			
		||||
	for i := range containers {
 | 
			
		||||
		pauseEvents[i] = strings.Fields(events[len(events)-len(containers)*2-1+i])
 | 
			
		||||
		unpauseEvents[i] = strings.Fields(events[len(events)-len(containers)-1+i])
 | 
			
		||||
	}
 | 
			
		||||
	for _, name := range containers {
 | 
			
		||||
		actions := eventActionsByIDAndType(c, events, name, "container")
 | 
			
		||||
 | 
			
		||||
	for _, pauseEvent := range pauseEvents {
 | 
			
		||||
		c.Assert(pauseEvent[len(pauseEvent)-1], checker.Equals, "pause")
 | 
			
		||||
		c.Assert(actions[len(actions)-2], checker.Equals, "pause")
 | 
			
		||||
		c.Assert(actions[len(actions)-1], checker.Equals, "unpause")
 | 
			
		||||
	}
 | 
			
		||||
	for _, unpauseEvent := range unpauseEvents {
 | 
			
		||||
		c.Assert(unpauseEvent[len(unpauseEvent)-1], checker.Equals, "unpause")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,10 +7,24 @@ import (
 | 
			
		|||
	"io"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/docker/docker/pkg/integration/checker"
 | 
			
		||||
	"github.com/go-check/check"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	reTimestamp  = `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{9}(:?(:?(:?-|\+)\d{2}:\d{2})|Z)`
 | 
			
		||||
	reEventType  = `(?P<eventType>\w+)`
 | 
			
		||||
	reAction     = `(?P<action>\w+)`
 | 
			
		||||
	reID         = `(?P<id>[^\s]+)`
 | 
			
		||||
	reAttributes = `(\s\((?P<attributes>[^\)]+)\))?`
 | 
			
		||||
	reString     = fmt.Sprintf(`\A%s\s%s\s%s\s%s%s\z`, reTimestamp, reEventType, reAction, reID, reAttributes)
 | 
			
		||||
 | 
			
		||||
	// eventCliRegexp is a regular expression that matches all possible event outputs in the cli
 | 
			
		||||
	eventCliRegexp = regexp.MustCompile(reString)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// eventMatcher is a function that tries to match an event input.
 | 
			
		||||
type eventMatcher func(text string)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,7 +39,11 @@ type eventObserver struct {
 | 
			
		|||
// without running it. Users must call `eventObserver.Start` to start the command.
 | 
			
		||||
func newEventObserver(c *check.C, args ...string) (*eventObserver, error) {
 | 
			
		||||
	since := daemonTime(c).Unix()
 | 
			
		||||
	return newEventObserverWithBacklog(c, since, args...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newEventObserverWithBacklog creates a new observer changing the start time of the backlog to return.
 | 
			
		||||
func newEventObserverWithBacklog(c *check.C, since int64, args ...string) (*eventObserver, error) {
 | 
			
		||||
	cmdArgs := []string{"events", "--since", strconv.FormatInt(since, 10)}
 | 
			
		||||
	if len(args) > 0 {
 | 
			
		||||
		cmdArgs = append(cmdArgs, args...)
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +69,7 @@ func (e *eventObserver) Start() error {
 | 
			
		|||
// Stop stops the events command.
 | 
			
		||||
func (e *eventObserver) Stop() {
 | 
			
		||||
	e.command.Process.Kill()
 | 
			
		||||
	e.command.Process.Release()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Match tries to match the events output with a given matcher.
 | 
			
		||||
| 
						 | 
				
			
			@ -76,3 +95,103 @@ func (e *eventObserver) TimeoutError(id, event string) error {
 | 
			
		|||
func (e *eventObserver) output() string {
 | 
			
		||||
	return e.buffer.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// matchEventLine matches a text with the event regular expression.
 | 
			
		||||
// It returns the action and true if the regular expression matches with the given id and event type.
 | 
			
		||||
// It returns an empty string and false if there is no match.
 | 
			
		||||
func matchEventLine(id, eventType string, actions map[string]chan bool) eventMatcher {
 | 
			
		||||
	return func(text string) {
 | 
			
		||||
		matches := parseEventText(text)
 | 
			
		||||
		if matches == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if matchIDAndEventType(matches, id, eventType) {
 | 
			
		||||
			if ch, ok := actions[matches["action"]]; ok {
 | 
			
		||||
				close(ch)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseEventText parses a line of events coming from the cli and returns
 | 
			
		||||
// the matchers in a map.
 | 
			
		||||
func parseEventText(text string) map[string]string {
 | 
			
		||||
	matches := eventCliRegexp.FindAllStringSubmatch(text, -1)
 | 
			
		||||
	if len(matches) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	names := eventCliRegexp.SubexpNames()
 | 
			
		||||
	md := map[string]string{}
 | 
			
		||||
	for i, n := range matches[0] {
 | 
			
		||||
		md[names[i]] = n
 | 
			
		||||
	}
 | 
			
		||||
	return md
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseEventAction parses an event text and returns the action.
 | 
			
		||||
// It fails if the text is not in the event format.
 | 
			
		||||
func parseEventAction(c *check.C, text string) string {
 | 
			
		||||
	matches := parseEventText(text)
 | 
			
		||||
	c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
	return matches["action"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// eventActionsByIDAndType returns the actions for a given id and type.
 | 
			
		||||
// It fails if the text is not in the event format.
 | 
			
		||||
func eventActionsByIDAndType(c *check.C, events []string, id, eventType string) []string {
 | 
			
		||||
	var filtered []string
 | 
			
		||||
	for _, event := range events {
 | 
			
		||||
		matches := parseEventText(event)
 | 
			
		||||
		c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
		if matchIDAndEventType(matches, id, eventType) {
 | 
			
		||||
			filtered = append(filtered, matches["action"])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return filtered
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// matchIDAndEventType returns true if an event matches a given id and type.
 | 
			
		||||
// It also resolves names in the event attributes if the id doesn't match.
 | 
			
		||||
func matchIDAndEventType(matches map[string]string, id, eventType string) bool {
 | 
			
		||||
	return matchEventID(matches, id) && matches["eventType"] == eventType
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func matchEventID(matches map[string]string, id string) bool {
 | 
			
		||||
	matchID := matches["id"] == id || strings.HasPrefix(matches["id"], id)
 | 
			
		||||
	if !matchID && matches["attributes"] != "" {
 | 
			
		||||
		// try matching a name in the attributes
 | 
			
		||||
		attributes := map[string]string{}
 | 
			
		||||
		for _, a := range strings.Split(matches["attributes"], ", ") {
 | 
			
		||||
			kv := strings.Split(a, "=")
 | 
			
		||||
			attributes[kv[0]] = kv[1]
 | 
			
		||||
		}
 | 
			
		||||
		matchID = attributes["name"] == id
 | 
			
		||||
	}
 | 
			
		||||
	return matchID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseEvents(c *check.C, out, match string) {
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	for _, event := range events {
 | 
			
		||||
		matches := parseEventText(event)
 | 
			
		||||
		c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
		matched, err := regexp.MatchString(match, matches["action"])
 | 
			
		||||
		c.Assert(err, checker.IsNil)
 | 
			
		||||
		c.Assert(matched, checker.True)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parseEventsWithID(c *check.C, out, match, id string) {
 | 
			
		||||
	events := strings.Split(strings.TrimSpace(out), "\n")
 | 
			
		||||
	for _, event := range events {
 | 
			
		||||
		matches := parseEventText(event)
 | 
			
		||||
		c.Assert(matches, checker.Not(checker.IsNil))
 | 
			
		||||
		c.Assert(matchEventID(matches, id), checker.True)
 | 
			
		||||
 | 
			
		||||
		matched, err := regexp.MatchString(match, matches["action"])
 | 
			
		||||
		c.Assert(err, checker.IsNil)
 | 
			
		||||
		c.Assert(matched, checker.True)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue