Files
teleport/lib/cache/notification.go
rosstimothy e81212dfcc Unify resource pagination (#56418)
This converts hand rolled collections of resources via ListFoo
RPCs in tctl and the cache to make use of clientutils.Resources.
The main driver for the change is consistency and simplifcation.
Having a single place that pagination logic exists should help
reduce the chances of any pagination bugs in the future. The helper
also reduces the amount of code needed at a call site to aggregate
resources.

The guts of clientutils.IterateResources have been moved to
clientutils.Resources which returns an iter.Seq2[T, error] instead
of the callback based approach. This makes the api simpler and allows
making use of the itertools/stream helpers. Additionally, the page
size used within clientutils.Resources was made to dynamically adjust
based on receiving any errors indicating that the max message size was
exceeding.
2025-07-08 12:05:28 +00:00

119 lines
4.6 KiB
Go

// Teleport
// Copyright (C) 2025 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cache
import (
"context"
"github.com/gravitational/trace"
"google.golang.org/protobuf/proto"
notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/clientutils"
"github.com/gravitational/teleport/lib/itertools/stream"
"github.com/gravitational/teleport/lib/services"
)
type userNotificationIndex string
const userNotificationNameIndex userNotificationIndex = "name"
func newUserNotificationCollection(upstream services.NotificationGetter, w types.WatchKind) (*collection[*notificationsv1.Notification, userNotificationIndex], error) {
if upstream == nil {
return nil, trace.BadParameter("missing parameter NotificationGetter")
}
return &collection[*notificationsv1.Notification, userNotificationIndex]{
store: newStore(
types.KindNotification,
proto.CloneOf[*notificationsv1.Notification],
map[userNotificationIndex]func(*notificationsv1.Notification) string{
userNotificationNameIndex: func(r *notificationsv1.Notification) string {
return r.GetMetadata().GetName()
},
}),
fetcher: func(ctx context.Context, loadSecrets bool) ([]*notificationsv1.Notification, error) {
out, err := stream.Collect(clientutils.Resources(ctx, upstream.ListUserNotifications))
return out, trace.Wrap(err)
},
watch: w,
}, nil
}
// ListUserNotifications returns a paginated list of user-specific notifications for all users.
func (c *Cache) ListUserNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.Notification, string, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListUserNotifications")
defer span.End()
lister := genericLister[*notificationsv1.Notification, userNotificationIndex]{
cache: c,
collection: c.collections.userNotifications,
index: userNotificationNameIndex,
upstreamList: c.Config.Notifications.ListUserNotifications,
nextToken: func(t *notificationsv1.Notification) string {
return t.GetMetadata().GetName()
},
}
out, next, err := lister.list(ctx, pageSize, startKey)
return out, next, trace.Wrap(err)
}
type globalNotificationIndex string
const globalNotificationNameIndex globalNotificationIndex = "name"
func newGlobalNotificationCollection(upstream services.NotificationGetter, w types.WatchKind) (*collection[*notificationsv1.GlobalNotification, globalNotificationIndex], error) {
if upstream == nil {
return nil, trace.BadParameter("missing parameter NotificationGetter")
}
return &collection[*notificationsv1.GlobalNotification, globalNotificationIndex]{
store: newStore(
types.KindGlobalNotification,
proto.CloneOf[*notificationsv1.GlobalNotification],
map[globalNotificationIndex]func(*notificationsv1.GlobalNotification) string{
globalNotificationNameIndex: func(r *notificationsv1.GlobalNotification) string {
return r.GetMetadata().GetName()
},
}),
fetcher: func(ctx context.Context, loadSecrets bool) ([]*notificationsv1.GlobalNotification, error) {
out, err := stream.Collect(clientutils.Resources(ctx, upstream.ListGlobalNotifications))
return out, trace.Wrap(err)
},
watch: w,
}, nil
}
// ListGlobalNotifications returns a paginated list of global notifications.
func (c *Cache) ListGlobalNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.GlobalNotification, string, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListGlobalNotifications")
defer span.End()
lister := genericLister[*notificationsv1.GlobalNotification, globalNotificationIndex]{
cache: c,
collection: c.collections.globalNotifications,
index: globalNotificationNameIndex,
upstreamList: c.Config.Notifications.ListGlobalNotifications,
nextToken: func(t *notificationsv1.GlobalNotification) string {
return t.GetMetadata().GetName()
},
}
out, next, err := lister.list(ctx, pageSize, startKey)
return out, next, trace.Wrap(err)
}