gopkg.in/docker/docker.v23@v23.0.11/daemon/events.go (about) 1 package daemon // import "github.com/docker/docker/daemon" 2 3 import ( 4 "context" 5 "strconv" 6 "strings" 7 "time" 8 9 "github.com/docker/docker/api/types/events" 10 "github.com/docker/docker/api/types/filters" 11 "github.com/docker/docker/container" 12 daemonevents "github.com/docker/docker/daemon/events" 13 "github.com/docker/docker/libnetwork" 14 gogotypes "github.com/gogo/protobuf/types" 15 swarmapi "github.com/moby/swarmkit/v2/api" 16 "github.com/sirupsen/logrus" 17 ) 18 19 var ( 20 clusterEventAction = map[swarmapi.WatchActionKind]string{ 21 swarmapi.WatchActionKindCreate: "create", 22 swarmapi.WatchActionKindUpdate: "update", 23 swarmapi.WatchActionKindRemove: "remove", 24 } 25 ) 26 27 // LogContainerEvent generates an event related to a container with only the default attributes. 28 func (daemon *Daemon) LogContainerEvent(container *container.Container, action string) { 29 daemon.LogContainerEventWithAttributes(container, action, map[string]string{}) 30 } 31 32 // LogContainerEventWithAttributes generates an event related to a container with specific given attributes. 33 func (daemon *Daemon) LogContainerEventWithAttributes(container *container.Container, action string, attributes map[string]string) { 34 copyAttributes(attributes, container.Config.Labels) 35 if container.Config.Image != "" { 36 attributes["image"] = container.Config.Image 37 } 38 attributes["name"] = strings.TrimLeft(container.Name, "/") 39 40 actor := events.Actor{ 41 ID: container.ID, 42 Attributes: attributes, 43 } 44 daemon.EventsService.Log(action, events.ContainerEventType, actor) 45 } 46 47 // LogPluginEvent generates an event related to a plugin with only the default attributes. 48 func (daemon *Daemon) LogPluginEvent(pluginID, refName, action string) { 49 daemon.LogPluginEventWithAttributes(pluginID, refName, action, map[string]string{}) 50 } 51 52 // LogPluginEventWithAttributes generates an event related to a plugin with specific given attributes. 53 func (daemon *Daemon) LogPluginEventWithAttributes(pluginID, refName, action string, attributes map[string]string) { 54 attributes["name"] = refName 55 actor := events.Actor{ 56 ID: pluginID, 57 Attributes: attributes, 58 } 59 daemon.EventsService.Log(action, events.PluginEventType, actor) 60 } 61 62 // LogVolumeEvent generates an event related to a volume. 63 func (daemon *Daemon) LogVolumeEvent(volumeID, action string, attributes map[string]string) { 64 actor := events.Actor{ 65 ID: volumeID, 66 Attributes: attributes, 67 } 68 daemon.EventsService.Log(action, events.VolumeEventType, actor) 69 } 70 71 // LogNetworkEvent generates an event related to a network with only the default attributes. 72 func (daemon *Daemon) LogNetworkEvent(nw libnetwork.Network, action string) { 73 daemon.LogNetworkEventWithAttributes(nw, action, map[string]string{}) 74 } 75 76 // LogNetworkEventWithAttributes generates an event related to a network with specific given attributes. 77 func (daemon *Daemon) LogNetworkEventWithAttributes(nw libnetwork.Network, action string, attributes map[string]string) { 78 attributes["name"] = nw.Name() 79 attributes["type"] = nw.Type() 80 actor := events.Actor{ 81 ID: nw.ID(), 82 Attributes: attributes, 83 } 84 daemon.EventsService.Log(action, events.NetworkEventType, actor) 85 } 86 87 // LogDaemonEventWithAttributes generates an event related to the daemon itself with specific given attributes. 88 func (daemon *Daemon) LogDaemonEventWithAttributes(action string, attributes map[string]string) { 89 if daemon.EventsService != nil { 90 if name := hostName(); name != "" { 91 attributes["name"] = name 92 } 93 daemon.EventsService.Log(action, events.DaemonEventType, events.Actor{ 94 ID: daemon.id, 95 Attributes: attributes, 96 }) 97 } 98 } 99 100 // SubscribeToEvents returns the currently record of events, a channel to stream new events from, and a function to cancel the stream of events. 101 func (daemon *Daemon) SubscribeToEvents(since, until time.Time, filter filters.Args) ([]events.Message, chan interface{}) { 102 ef := daemonevents.NewFilter(filter) 103 return daemon.EventsService.SubscribeTopic(since, until, ef) 104 } 105 106 // UnsubscribeFromEvents stops the event subscription for a client by closing the 107 // channel where the daemon sends events to. 108 func (daemon *Daemon) UnsubscribeFromEvents(listener chan interface{}) { 109 daemon.EventsService.Evict(listener) 110 } 111 112 // copyAttributes guarantees that labels are not mutated by event triggers. 113 func copyAttributes(attributes, labels map[string]string) { 114 if labels == nil { 115 return 116 } 117 for k, v := range labels { 118 attributes[k] = v 119 } 120 } 121 122 // ProcessClusterNotifications gets changes from store and add them to event list 123 func (daemon *Daemon) ProcessClusterNotifications(ctx context.Context, watchStream chan *swarmapi.WatchMessage) { 124 for { 125 select { 126 case <-ctx.Done(): 127 return 128 case message, ok := <-watchStream: 129 if !ok { 130 logrus.Debug("cluster event channel has stopped") 131 return 132 } 133 daemon.generateClusterEvent(message) 134 } 135 } 136 } 137 138 func (daemon *Daemon) generateClusterEvent(msg *swarmapi.WatchMessage) { 139 for _, event := range msg.Events { 140 if event.Object == nil { 141 logrus.Errorf("event without object: %v", event) 142 continue 143 } 144 switch v := event.Object.GetObject().(type) { 145 case *swarmapi.Object_Node: 146 daemon.logNodeEvent(event.Action, v.Node, event.OldObject.GetNode()) 147 case *swarmapi.Object_Service: 148 daemon.logServiceEvent(event.Action, v.Service, event.OldObject.GetService()) 149 case *swarmapi.Object_Network: 150 daemon.logNetworkEvent(event.Action, v.Network, event.OldObject.GetNetwork()) 151 case *swarmapi.Object_Secret: 152 daemon.logSecretEvent(event.Action, v.Secret, event.OldObject.GetSecret()) 153 case *swarmapi.Object_Config: 154 daemon.logConfigEvent(event.Action, v.Config, event.OldObject.GetConfig()) 155 default: 156 logrus.Warnf("unrecognized event: %v", event) 157 } 158 } 159 } 160 161 func (daemon *Daemon) logNetworkEvent(action swarmapi.WatchActionKind, net *swarmapi.Network, oldNet *swarmapi.Network) { 162 attributes := map[string]string{ 163 "name": net.Spec.Annotations.Name, 164 } 165 eventTime := eventTimestamp(net.Meta, action) 166 daemon.logClusterEvent(action, net.ID, "network", attributes, eventTime) 167 } 168 169 func (daemon *Daemon) logSecretEvent(action swarmapi.WatchActionKind, secret *swarmapi.Secret, oldSecret *swarmapi.Secret) { 170 attributes := map[string]string{ 171 "name": secret.Spec.Annotations.Name, 172 } 173 eventTime := eventTimestamp(secret.Meta, action) 174 daemon.logClusterEvent(action, secret.ID, "secret", attributes, eventTime) 175 } 176 177 func (daemon *Daemon) logConfigEvent(action swarmapi.WatchActionKind, config *swarmapi.Config, oldConfig *swarmapi.Config) { 178 attributes := map[string]string{ 179 "name": config.Spec.Annotations.Name, 180 } 181 eventTime := eventTimestamp(config.Meta, action) 182 daemon.logClusterEvent(action, config.ID, "config", attributes, eventTime) 183 } 184 185 func (daemon *Daemon) logNodeEvent(action swarmapi.WatchActionKind, node *swarmapi.Node, oldNode *swarmapi.Node) { 186 name := node.Spec.Annotations.Name 187 if name == "" && node.Description != nil { 188 name = node.Description.Hostname 189 } 190 attributes := map[string]string{ 191 "name": name, 192 } 193 eventTime := eventTimestamp(node.Meta, action) 194 // In an update event, display the changes in attributes 195 if action == swarmapi.WatchActionKindUpdate && oldNode != nil { 196 if node.Spec.Availability != oldNode.Spec.Availability { 197 attributes["availability.old"] = strings.ToLower(oldNode.Spec.Availability.String()) 198 attributes["availability.new"] = strings.ToLower(node.Spec.Availability.String()) 199 } 200 if node.Role != oldNode.Role { 201 attributes["role.old"] = strings.ToLower(oldNode.Role.String()) 202 attributes["role.new"] = strings.ToLower(node.Role.String()) 203 } 204 if node.Status.State != oldNode.Status.State { 205 attributes["state.old"] = strings.ToLower(oldNode.Status.State.String()) 206 attributes["state.new"] = strings.ToLower(node.Status.State.String()) 207 } 208 // This handles change within manager role 209 if node.ManagerStatus != nil && oldNode.ManagerStatus != nil { 210 // leader change 211 if node.ManagerStatus.Leader != oldNode.ManagerStatus.Leader { 212 if node.ManagerStatus.Leader { 213 attributes["leader.old"] = "false" 214 attributes["leader.new"] = "true" 215 } else { 216 attributes["leader.old"] = "true" 217 attributes["leader.new"] = "false" 218 } 219 } 220 if node.ManagerStatus.Reachability != oldNode.ManagerStatus.Reachability { 221 attributes["reachability.old"] = strings.ToLower(oldNode.ManagerStatus.Reachability.String()) 222 attributes["reachability.new"] = strings.ToLower(node.ManagerStatus.Reachability.String()) 223 } 224 } 225 } 226 227 daemon.logClusterEvent(action, node.ID, "node", attributes, eventTime) 228 } 229 230 func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service *swarmapi.Service, oldService *swarmapi.Service) { 231 attributes := map[string]string{ 232 "name": service.Spec.Annotations.Name, 233 } 234 eventTime := eventTimestamp(service.Meta, action) 235 236 if action == swarmapi.WatchActionKindUpdate && oldService != nil { 237 // check image 238 if x, ok := service.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok { 239 containerSpec := x.Container 240 if y, ok := oldService.Spec.Task.GetRuntime().(*swarmapi.TaskSpec_Container); ok { 241 oldContainerSpec := y.Container 242 if containerSpec.Image != oldContainerSpec.Image { 243 attributes["image.old"] = oldContainerSpec.Image 244 attributes["image.new"] = containerSpec.Image 245 } 246 } else { 247 // This should not happen. 248 logrus.Errorf("service %s runtime changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.Task.GetRuntime(), service.Spec.Task.GetRuntime()) 249 } 250 } 251 // check replicated count change 252 if x, ok := service.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok { 253 replicas := x.Replicated.Replicas 254 if y, ok := oldService.Spec.GetMode().(*swarmapi.ServiceSpec_Replicated); ok { 255 oldReplicas := y.Replicated.Replicas 256 if replicas != oldReplicas { 257 attributes["replicas.old"] = strconv.FormatUint(oldReplicas, 10) 258 attributes["replicas.new"] = strconv.FormatUint(replicas, 10) 259 } 260 } else { 261 // This should not happen. 262 logrus.Errorf("service %s mode changed from %T to %T", service.Spec.Annotations.Name, oldService.Spec.GetMode(), service.Spec.GetMode()) 263 } 264 } 265 if service.UpdateStatus != nil { 266 if oldService.UpdateStatus == nil { 267 attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String()) 268 } else if service.UpdateStatus.State != oldService.UpdateStatus.State { 269 attributes["updatestate.old"] = strings.ToLower(oldService.UpdateStatus.State.String()) 270 attributes["updatestate.new"] = strings.ToLower(service.UpdateStatus.State.String()) 271 } 272 } 273 } 274 daemon.logClusterEvent(action, service.ID, "service", attributes, eventTime) 275 } 276 277 func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id, eventType string, attributes map[string]string, eventTime time.Time) { 278 actor := events.Actor{ 279 ID: id, 280 Attributes: attributes, 281 } 282 283 jm := events.Message{ 284 Action: clusterEventAction[action], 285 Type: eventType, 286 Actor: actor, 287 Scope: "swarm", 288 Time: eventTime.UTC().Unix(), 289 TimeNano: eventTime.UTC().UnixNano(), 290 } 291 daemon.EventsService.PublishMessage(jm) 292 } 293 294 func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time { 295 var eventTime time.Time 296 switch action { 297 case swarmapi.WatchActionKindCreate: 298 eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt) 299 case swarmapi.WatchActionKindUpdate: 300 eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt) 301 case swarmapi.WatchActionKindRemove: 302 // There is no timestamp from store message for remove operations. 303 // Use current time. 304 eventTime = time.Now() 305 } 306 return eventTime 307 }