daemon: prevent invalid swarm events from unknown action kinds

- Replace clusterEventAction map with switch for exhaustive checking
- Make eventTimestamp() switch exhaustive with explicit Unknown handling
- Use early returns instead of fallthrough in eventTimestamp
- Add test cases for unknown and invalid action kinds
- Rename test field 'name' to 'doc' and loop variable 'tt' to 'tc'

This change ensures that:
- Invalid action kinds are filtered out before publishing events
- The linter can catch missing action kinds
- All code paths use valid timestamps

Signed-off-by: hiroto.toyoda <hiroto.toyoda@dena.com>
This commit is contained in:
hiroto.toyoda committed 2026-02-22 15:26:24 +09:00
1 parent f222c1ed05
commit 2d0676b8bb
2 files changed
+99 -13

No files matched your search

+26 -13
View File
@@ -241,15 +241,25 @@ func (daemon *Daemon) logServiceEvent(action swarmapi.WatchActionKind, service *
daemon.logClusterEvent(action, service.ID, events.ServiceEventType, eventTime, attributes)
}
var clusterEventAction = map[swarmapi.WatchActionKind]events.Action{
swarmapi.WatchActionKindCreate: events.ActionCreate,
swarmapi.WatchActionKindUpdate: events.ActionUpdate,
swarmapi.WatchActionKindRemove: events.ActionRemove,
}
func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id string, eventType events.Type, eventTime time.Time, attributes map[string]string) {
var eventAction events.Action
switch action {
case swarmapi.WatchActionKindCreate:
eventAction = events.ActionCreate
case swarmapi.WatchActionKindUpdate:
eventAction = events.ActionUpdate
case swarmapi.WatchActionKindRemove:
eventAction = events.ActionRemove
case swarmapi.WatchActionKindUnknown:
// Unknown action kind - skip publishing invalid event
return
default:
// Unknown action kind - skip publishing invalid event
return
}
daemon.EventsService.PublishMessage(events.Message{
Action: clusterEventAction[action],
Action: eventAction,
Type: eventType,
Actor: events.Actor{
ID: id,
@@ -262,18 +272,21 @@ func (daemon *Daemon) logClusterEvent(action swarmapi.WatchActionKind, id string
}
func eventTimestamp(meta swarmapi.Meta, action swarmapi.WatchActionKind) time.Time {
var eventTime time.Time
switch action {
case swarmapi.WatchActionKindCreate:
eventTime, _ = gogotypes.TimestampFromProto(meta.CreatedAt)
eventTime, _ := gogotypes.TimestampFromProto(meta.CreatedAt)
return eventTime
case swarmapi.WatchActionKindUpdate:
eventTime, _ = gogotypes.TimestampFromProto(meta.UpdatedAt)
eventTime, _ := gogotypes.TimestampFromProto(meta.UpdatedAt)
return eventTime
case swarmapi.WatchActionKindRemove:
// There is no timestamp from store message for remove operations.
// Use current time.
eventTime = time.Now()
return time.Now()
case swarmapi.WatchActionKindUnknown:
return time.Now()
default:
// TODO(thaJeztah): make switch exhaustive: anything to do for swarmapi.WatchActionKindUnknown or "other" ?
// For any unexpected action kinds, use current time as fallback.
return time.Now()
}
return eventTime
}
+73
View File
@@ -4,10 +4,12 @@ import (
"testing"
"time"
gogotypes "github.com/gogo/protobuf/types"
containertypes "github.com/moby/moby/api/types/container"
eventtypes "github.com/moby/moby/api/types/events"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/daemon/events"
swarmapi "github.com/moby/swarmkit/v2/api"
)
func TestLogContainerEventCopyLabels(t *testing.T) {
@@ -87,3 +89,74 @@ func validateTestAttributes(t *testing.T, l chan any, expectedAttributesToTest m
t.Fatal("LogEvent test timed out")
}
}
func TestEventTimestamp(t *testing.T) {
now := time.Now()
createdAt := &gogotypes.Timestamp{Seconds: now.Unix(), Nanos: int32(now.Nanosecond())}
updatedAt := &gogotypes.Timestamp{Seconds: now.Add(time.Hour).Unix(), Nanos: int32(now.Add(time.Hour).Nanosecond())}
tests := []struct {
doc string
meta swarmapi.Meta
action swarmapi.WatchActionKind
check func(t *testing.T, result time.Time)
}{
{
doc: "Create action uses CreatedAt timestamp",
meta: swarmapi.Meta{CreatedAt: createdAt},
action: swarmapi.WatchActionKindCreate,
check: func(t *testing.T, result time.Time) {
if result.Unix() != now.Unix() {
t.Errorf("expected CreatedAt timestamp, got %v", result)
}
},
},
{
doc: "Update action uses UpdatedAt timestamp",
meta: swarmapi.Meta{UpdatedAt: updatedAt},
action: swarmapi.WatchActionKindUpdate,
check: func(t *testing.T, result time.Time) {
if result.Unix() != now.Add(time.Hour).Unix() {
t.Errorf("expected UpdatedAt timestamp, got %v", result)
}
},
},
{
doc: "Remove action uses current time",
meta: swarmapi.Meta{},
action: swarmapi.WatchActionKindRemove,
check: func(t *testing.T, result time.Time) {
if result.IsZero() {
t.Error("expected non-zero timestamp for Remove action")
}
},
},
{
doc: "Unknown action returns valid timestamp",
meta: swarmapi.Meta{},
action: swarmapi.WatchActionKindUnknown,
check: func(t *testing.T, result time.Time) {
if result.IsZero() {
t.Error("expected non-zero timestamp for Unknown action")
}
},
},
{
doc: "Invalid action falls back to current time",
meta: swarmapi.Meta{},
action: swarmapi.WatchActionKind(123456789),
check: func(t *testing.T, result time.Time) {
if result.IsZero() {
t.Error("expected non-zero timestamp for invalid action")
}
},
},
}
for _, tc := range tests {
t.Run(tc.doc, func(t *testing.T) {
result := eventTimestamp(tc.meta, tc.action)
tc.check(t, result)
})
}
}