mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
Enhance the tctl notifications command (#48829)
- Add the ability create notifications with a custom TTL (previously we relied on the server-provided default of 30 days) - Add the ability to create notifications with custom labels - Add (non-system) labels to the `tctl notifications ls` output - Add the ability to filter by label when listing notifications Closes #48631
This commit is contained in:
@@ -228,6 +228,8 @@ type NotificationFilters struct {
|
||||
GlobalOnly bool `protobuf:"varint,2,opt,name=global_only,json=globalOnly,proto3" json:"global_only,omitempty"`
|
||||
// user_created_only is whether to only list user-created notifications (ie. notifications created by an admin via the tctl interface).
|
||||
UserCreatedOnly bool `protobuf:"varint,3,opt,name=user_created_only,json=userCreatedOnly,proto3" json:"user_created_only,omitempty"`
|
||||
// labels is used to request only notifications with specific labels.
|
||||
Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
func (x *NotificationFilters) Reset() {
|
||||
@@ -281,6 +283,13 @@ func (x *NotificationFilters) GetUserCreatedOnly() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NotificationFilters) GetLabels() map[string]string {
|
||||
if x != nil {
|
||||
return x.Labels
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListNotificationsResponse is the response from listing a user's notifications.
|
||||
type ListNotificationsResponse struct {
|
||||
state protoimpl.MessageState
|
||||
@@ -592,15 +601,24 @@ var file_teleport_notifications_v1_notifications_service_proto_rawDesc = []byte{
|
||||
0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x52, 0x07, 0x66, 0x69, 0x6c, 0x74,
|
||||
0x65, 0x72, 0x73, 0x22, 0x7e, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c,
|
||||
0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x67, 0x6c, 0x6f,
|
||||
0x62, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x5f,
|
||||
0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x4f,
|
||||
0x6e, 0x6c, 0x79, 0x22, 0x80, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69,
|
||||
0x65, 0x72, 0x73, 0x22, 0x8d, 0x02, 0x0a, 0x13, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x75,
|
||||
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75,
|
||||
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x6c, 0x6f, 0x62, 0x61,
|
||||
0x6c, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x67, 0x6c,
|
||||
0x6f, 0x62, 0x61, 0x6c, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72,
|
||||
0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x08, 0x52, 0x0f, 0x75, 0x73, 0x65, 0x72, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
|
||||
0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x52, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e,
|
||||
0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x46, 0x69, 0x6c,
|
||||
0x74, 0x65, 0x72, 0x73, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
|
||||
0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65,
|
||||
0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
|
||||
0x02, 0x38, 0x01, 0x22, 0x80, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x4d, 0x0a, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70,
|
||||
@@ -731,7 +749,7 @@ func file_teleport_notifications_v1_notifications_service_proto_rawDescGZIP() []
|
||||
return file_teleport_notifications_v1_notifications_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_teleport_notifications_v1_notifications_service_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
|
||||
var file_teleport_notifications_v1_notifications_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
|
||||
var file_teleport_notifications_v1_notifications_service_proto_goTypes = []any{
|
||||
(*CreateUserNotificationRequest)(nil), // 0: teleport.notifications.v1.CreateUserNotificationRequest
|
||||
(*DeleteUserNotificationRequest)(nil), // 1: teleport.notifications.v1.DeleteUserNotificationRequest
|
||||
@@ -742,40 +760,42 @@ var file_teleport_notifications_v1_notifications_service_proto_goTypes = []any{
|
||||
(*DeleteGlobalNotificationRequest)(nil), // 6: teleport.notifications.v1.DeleteGlobalNotificationRequest
|
||||
(*UpsertUserNotificationStateRequest)(nil), // 7: teleport.notifications.v1.UpsertUserNotificationStateRequest
|
||||
(*UpsertUserLastSeenNotificationRequest)(nil), // 8: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
|
||||
(*Notification)(nil), // 9: teleport.notifications.v1.Notification
|
||||
(*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp
|
||||
(*GlobalNotification)(nil), // 11: teleport.notifications.v1.GlobalNotification
|
||||
(*UserNotificationState)(nil), // 12: teleport.notifications.v1.UserNotificationState
|
||||
(*UserLastSeenNotification)(nil), // 13: teleport.notifications.v1.UserLastSeenNotification
|
||||
(*emptypb.Empty)(nil), // 14: google.protobuf.Empty
|
||||
nil, // 9: teleport.notifications.v1.NotificationFilters.LabelsEntry
|
||||
(*Notification)(nil), // 10: teleport.notifications.v1.Notification
|
||||
(*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
|
||||
(*GlobalNotification)(nil), // 12: teleport.notifications.v1.GlobalNotification
|
||||
(*UserNotificationState)(nil), // 13: teleport.notifications.v1.UserNotificationState
|
||||
(*UserLastSeenNotification)(nil), // 14: teleport.notifications.v1.UserLastSeenNotification
|
||||
(*emptypb.Empty)(nil), // 15: google.protobuf.Empty
|
||||
}
|
||||
var file_teleport_notifications_v1_notifications_service_proto_depIdxs = []int32{
|
||||
9, // 0: teleport.notifications.v1.CreateUserNotificationRequest.notification:type_name -> teleport.notifications.v1.Notification
|
||||
10, // 0: teleport.notifications.v1.CreateUserNotificationRequest.notification:type_name -> teleport.notifications.v1.Notification
|
||||
3, // 1: teleport.notifications.v1.ListNotificationsRequest.filters:type_name -> teleport.notifications.v1.NotificationFilters
|
||||
9, // 2: teleport.notifications.v1.ListNotificationsResponse.notifications:type_name -> teleport.notifications.v1.Notification
|
||||
10, // 3: teleport.notifications.v1.ListNotificationsResponse.user_last_seen_notification_timestamp:type_name -> google.protobuf.Timestamp
|
||||
11, // 4: teleport.notifications.v1.CreateGlobalNotificationRequest.global_notification:type_name -> teleport.notifications.v1.GlobalNotification
|
||||
12, // 5: teleport.notifications.v1.UpsertUserNotificationStateRequest.user_notification_state:type_name -> teleport.notifications.v1.UserNotificationState
|
||||
13, // 6: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest.user_last_seen_notification:type_name -> teleport.notifications.v1.UserLastSeenNotification
|
||||
0, // 7: teleport.notifications.v1.NotificationService.CreateUserNotification:input_type -> teleport.notifications.v1.CreateUserNotificationRequest
|
||||
1, // 8: teleport.notifications.v1.NotificationService.DeleteUserNotification:input_type -> teleport.notifications.v1.DeleteUserNotificationRequest
|
||||
5, // 9: teleport.notifications.v1.NotificationService.CreateGlobalNotification:input_type -> teleport.notifications.v1.CreateGlobalNotificationRequest
|
||||
6, // 10: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:input_type -> teleport.notifications.v1.DeleteGlobalNotificationRequest
|
||||
2, // 11: teleport.notifications.v1.NotificationService.ListNotifications:input_type -> teleport.notifications.v1.ListNotificationsRequest
|
||||
7, // 12: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:input_type -> teleport.notifications.v1.UpsertUserNotificationStateRequest
|
||||
8, // 13: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:input_type -> teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
|
||||
9, // 14: teleport.notifications.v1.NotificationService.CreateUserNotification:output_type -> teleport.notifications.v1.Notification
|
||||
14, // 15: teleport.notifications.v1.NotificationService.DeleteUserNotification:output_type -> google.protobuf.Empty
|
||||
11, // 16: teleport.notifications.v1.NotificationService.CreateGlobalNotification:output_type -> teleport.notifications.v1.GlobalNotification
|
||||
14, // 17: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:output_type -> google.protobuf.Empty
|
||||
4, // 18: teleport.notifications.v1.NotificationService.ListNotifications:output_type -> teleport.notifications.v1.ListNotificationsResponse
|
||||
12, // 19: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:output_type -> teleport.notifications.v1.UserNotificationState
|
||||
13, // 20: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:output_type -> teleport.notifications.v1.UserLastSeenNotification
|
||||
14, // [14:21] is the sub-list for method output_type
|
||||
7, // [7:14] is the sub-list for method input_type
|
||||
7, // [7:7] is the sub-list for extension type_name
|
||||
7, // [7:7] is the sub-list for extension extendee
|
||||
0, // [0:7] is the sub-list for field type_name
|
||||
9, // 2: teleport.notifications.v1.NotificationFilters.labels:type_name -> teleport.notifications.v1.NotificationFilters.LabelsEntry
|
||||
10, // 3: teleport.notifications.v1.ListNotificationsResponse.notifications:type_name -> teleport.notifications.v1.Notification
|
||||
11, // 4: teleport.notifications.v1.ListNotificationsResponse.user_last_seen_notification_timestamp:type_name -> google.protobuf.Timestamp
|
||||
12, // 5: teleport.notifications.v1.CreateGlobalNotificationRequest.global_notification:type_name -> teleport.notifications.v1.GlobalNotification
|
||||
13, // 6: teleport.notifications.v1.UpsertUserNotificationStateRequest.user_notification_state:type_name -> teleport.notifications.v1.UserNotificationState
|
||||
14, // 7: teleport.notifications.v1.UpsertUserLastSeenNotificationRequest.user_last_seen_notification:type_name -> teleport.notifications.v1.UserLastSeenNotification
|
||||
0, // 8: teleport.notifications.v1.NotificationService.CreateUserNotification:input_type -> teleport.notifications.v1.CreateUserNotificationRequest
|
||||
1, // 9: teleport.notifications.v1.NotificationService.DeleteUserNotification:input_type -> teleport.notifications.v1.DeleteUserNotificationRequest
|
||||
5, // 10: teleport.notifications.v1.NotificationService.CreateGlobalNotification:input_type -> teleport.notifications.v1.CreateGlobalNotificationRequest
|
||||
6, // 11: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:input_type -> teleport.notifications.v1.DeleteGlobalNotificationRequest
|
||||
2, // 12: teleport.notifications.v1.NotificationService.ListNotifications:input_type -> teleport.notifications.v1.ListNotificationsRequest
|
||||
7, // 13: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:input_type -> teleport.notifications.v1.UpsertUserNotificationStateRequest
|
||||
8, // 14: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:input_type -> teleport.notifications.v1.UpsertUserLastSeenNotificationRequest
|
||||
10, // 15: teleport.notifications.v1.NotificationService.CreateUserNotification:output_type -> teleport.notifications.v1.Notification
|
||||
15, // 16: teleport.notifications.v1.NotificationService.DeleteUserNotification:output_type -> google.protobuf.Empty
|
||||
12, // 17: teleport.notifications.v1.NotificationService.CreateGlobalNotification:output_type -> teleport.notifications.v1.GlobalNotification
|
||||
15, // 18: teleport.notifications.v1.NotificationService.DeleteGlobalNotification:output_type -> google.protobuf.Empty
|
||||
4, // 19: teleport.notifications.v1.NotificationService.ListNotifications:output_type -> teleport.notifications.v1.ListNotificationsResponse
|
||||
13, // 20: teleport.notifications.v1.NotificationService.UpsertUserNotificationState:output_type -> teleport.notifications.v1.UserNotificationState
|
||||
14, // 21: teleport.notifications.v1.NotificationService.UpsertUserLastSeenNotification:output_type -> teleport.notifications.v1.UserLastSeenNotification
|
||||
15, // [15:22] is the sub-list for method output_type
|
||||
8, // [8:15] is the sub-list for method input_type
|
||||
8, // [8:8] is the sub-list for extension type_name
|
||||
8, // [8:8] is the sub-list for extension extendee
|
||||
0, // [0:8] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_teleport_notifications_v1_notifications_service_proto_init() }
|
||||
@@ -790,7 +810,7 @@ func file_teleport_notifications_v1_notifications_service_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_teleport_notifications_v1_notifications_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 9,
|
||||
NumMessages: 10,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -82,6 +82,8 @@ message NotificationFilters {
|
||||
bool global_only = 2;
|
||||
// user_created_only is whether to only list user-created notifications (ie. notifications created by an admin via the tctl interface).
|
||||
bool user_created_only = 3;
|
||||
// labels is used to request only notifications with specific labels.
|
||||
map<string, string> labels = 4;
|
||||
}
|
||||
|
||||
// ListNotificationsResponse is the response from listing a user's notifications.
|
||||
|
||||
@@ -107,12 +107,25 @@ func NewService(cfg ServiceConfig) (*Service, error) {
|
||||
|
||||
// ListNotifications returns a paginated list of notifications which match the user.
|
||||
func (s *Service) ListNotifications(ctx context.Context, req *notificationsv1.ListNotificationsRequest) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
labelsMatch := func(resourceLabels map[string]string) bool {
|
||||
if req.Filters == nil || len(req.Filters.Labels) == 0 {
|
||||
// no labels to match against
|
||||
return true
|
||||
}
|
||||
for k, v := range req.Filters.Labels {
|
||||
if resourceLabels[k] != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
if req.Filters != nil {
|
||||
if req.Filters.GlobalOnly {
|
||||
return s.listGlobalNotifications(ctx, req.Filters.UserCreatedOnly, req.PageToken, req.PageSize)
|
||||
return s.listGlobalNotifications(ctx, req)
|
||||
}
|
||||
if req.Filters.Username != "" {
|
||||
return s.listUserSpecificNotificationsForUser(ctx, req.Filters.Username, req.Filters.UserCreatedOnly, req.PageToken, req.PageSize)
|
||||
return s.listUserSpecificNotificationsForUser(ctx, req)
|
||||
}
|
||||
|
||||
return nil, trace.BadParameter("Invalid filters were provided, exactly one of GlobalOnly or Username must be defined.")
|
||||
@@ -168,6 +181,10 @@ func (s *Service) ListNotifications(ctx context.Context, req *notificationsv1.Li
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !labelsMatch(n.GetMetadata().GetLabels()) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !userNotifMatchFn(n) {
|
||||
return nil, false
|
||||
}
|
||||
@@ -184,6 +201,10 @@ func (s *Service) ListNotifications(ctx context.Context, req *notificationsv1.Li
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !labelsMatch(gn.GetMetadata().GetLabels()) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if !s.matchGlobalNotification(ctx, authCtx, gn, notificationStatesMap) {
|
||||
return nil, false
|
||||
}
|
||||
@@ -583,8 +604,8 @@ func (s *Service) DeleteUserNotification(ctx context.Context, req *notifications
|
||||
}
|
||||
|
||||
// listUserSpecificNotificationsForUser returns a paginated list of all user-specific notifications for a user. This should only be used by admins.
|
||||
func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, username string, userCreatedOnly bool, pageToken string, pageSize int32) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
if username == "" {
|
||||
func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, req *notificationsv1.ListNotificationsRequest) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
if req.GetFilters().GetUsername() == "" {
|
||||
return nil, trace.BadParameter("missing username")
|
||||
}
|
||||
|
||||
@@ -602,15 +623,21 @@ func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, user
|
||||
}
|
||||
|
||||
stream := stream.FilterMap(
|
||||
s.userNotificationCache.StreamUserNotifications(ctx, username, pageToken),
|
||||
s.userNotificationCache.StreamUserNotifications(ctx, req.GetFilters().GetUsername(), req.GetPageToken()),
|
||||
func(n *notificationsv1.Notification) (*notificationsv1.Notification, bool) {
|
||||
// If only user-created notifications are requested, filter by the user-creatd subkinds.
|
||||
if userCreatedOnly &&
|
||||
// If only user-created notifications are requested, filter by the user-created subkinds.
|
||||
if req.GetFilters().GetUserCreatedOnly() &&
|
||||
n.GetSubKind() != types.NotificationUserCreatedInformationalSubKind &&
|
||||
n.GetSubKind() != types.NotificationUserCreatedWarningSubKind {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for k, v := range req.GetFilters().GetLabels() {
|
||||
if n.GetMetadata().GetLabels()[k] != v {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
return n, true
|
||||
})
|
||||
|
||||
@@ -621,7 +648,7 @@ func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, user
|
||||
item := stream.Item()
|
||||
if item != nil {
|
||||
notifications = append(notifications, item)
|
||||
if len(notifications) == int(pageSize) {
|
||||
if len(notifications) == int(req.GetPageSize()) {
|
||||
nextKey = item.GetMetadata().GetName()
|
||||
break
|
||||
}
|
||||
@@ -635,7 +662,7 @@ func (s *Service) listUserSpecificNotificationsForUser(ctx context.Context, user
|
||||
}
|
||||
|
||||
// listGlobalNotifications returns a paginated list of all global notifications. This should only be used by admins.
|
||||
func (s *Service) listGlobalNotifications(ctx context.Context, userCreatedOnly bool, pageToken string, pageSize int32) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
func (s *Service) listGlobalNotifications(ctx context.Context, req *notificationsv1.ListNotificationsRequest) (*notificationsv1.ListNotificationsResponse, error) {
|
||||
authCtx, err := s.authorizer.Authorize(ctx)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -646,15 +673,24 @@ func (s *Service) listGlobalNotifications(ctx context.Context, userCreatedOnly b
|
||||
}
|
||||
|
||||
stream := stream.FilterMap(
|
||||
s.globalNotificationCache.StreamGlobalNotifications(ctx, pageToken),
|
||||
s.globalNotificationCache.StreamGlobalNotifications(ctx, req.GetPageToken()),
|
||||
func(gn *notificationsv1.GlobalNotification) (*notificationsv1.GlobalNotification, bool) {
|
||||
// If only user-created notifications are requested, filter by the user-creatd subkinds.
|
||||
if userCreatedOnly &&
|
||||
if req.GetFilters().GetUserCreatedOnly() &&
|
||||
gn.GetSpec().GetNotification().GetSubKind() != types.NotificationUserCreatedInformationalSubKind &&
|
||||
gn.GetSpec().GetNotification().GetSubKind() != types.NotificationUserCreatedWarningSubKind {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Pay special attention to the fact that we match against labels on the
|
||||
// inner-notification resource spec, not the labels in the GlobalNotification's
|
||||
// resource metadata.
|
||||
for k, v := range req.GetFilters().GetLabels() {
|
||||
if gn.GetSpec().GetNotification().GetMetadata().GetLabels()[k] != v {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
return gn, true
|
||||
})
|
||||
|
||||
@@ -669,7 +705,7 @@ func (s *Service) listGlobalNotifications(ctx context.Context, userCreatedOnly b
|
||||
|
||||
notifications = append(notifications, notification)
|
||||
|
||||
if len(notifications) == int(pageSize) {
|
||||
if len(notifications) == int(req.GetPageSize()) {
|
||||
nextKey = item.GetMetadata().GetName()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
"github.com/alecthomas/kingpin/v2"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/gravitational/trace/trail"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/defaults"
|
||||
@@ -38,8 +39,10 @@ import (
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/asciitable"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
libclient "github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/service/servicecfg"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/teleport/tool/common"
|
||||
)
|
||||
|
||||
// NotificationCommand implements the `tctl notifications` family of commands.
|
||||
@@ -58,6 +61,8 @@ type NotificationCommand struct {
|
||||
|
||||
title string
|
||||
content string
|
||||
labels string
|
||||
ttl time.Duration
|
||||
|
||||
// stdout allows to switch the standard output source. Used in tests.
|
||||
stdout io.Writer
|
||||
@@ -74,11 +79,14 @@ func (n *NotificationCommand) Initialize(app *kingpin.Application, _ *servicecfg
|
||||
n.create.Flag("title", "Set the notification's title.").Short('t').Required().StringVar(&n.title)
|
||||
n.create.Flag("content", "Set the notification's content.").Required().StringVar(&n.content)
|
||||
n.create.Flag("warning", "Set whether this notification is a warning notification.").BoolVar(&n.warning)
|
||||
n.create.Flag("ttl", "Time duration after which the notification expires (default 30 days).").Default("30d").DurationVar(&n.ttl)
|
||||
n.create.Flag("labels", "List of labels to attach to the notification. For example: key1=value1,key2=value2.").StringVar(&n.labels)
|
||||
|
||||
n.ls = notif.Command("ls", "List notifications which were manually created using `tctl notifications create`. By default, this will list notifications capable of targeting multiple users, such as role-based ones. To list notifications directed only at a specific user, use the --user flag. To include notifications generated by Teleport, use --all.")
|
||||
n.ls.Flag("user", "Set which user to list user-specific notifications for.").StringVar(&n.user)
|
||||
n.ls.Flag("format", "Output format, 'yaml', 'json', or 'text'").Default(teleport.Text).EnumVar(&n.format, teleport.YAML, teleport.JSON, teleport.Text)
|
||||
n.ls.Flag("all", "Set whether all notifications should be included, including those generated by Teleport, as opposed to solely those created using `tctl notifications create`.").BoolVar(&n.allNotifications)
|
||||
n.ls.Flag("labels", labelHelp).StringVar(&n.labels)
|
||||
|
||||
n.rm = notif.Command("rm", "Remove a cluster notification.").Alias("remove")
|
||||
n.rm.Flag("user", "The user the notification to remove belongs to, if any.").StringVar(&n.user)
|
||||
@@ -108,11 +116,17 @@ func (n *NotificationCommand) TryRun(ctx context.Context, cmd string, client *au
|
||||
|
||||
// Create creates a new notification.
|
||||
func (n *NotificationCommand) Create(ctx context.Context, client *authclient.Client) error {
|
||||
labels, err := libclient.ParseLabelSpec(n.labels)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
labels[types.NotificationTitleLabel] = n.title
|
||||
labels[types.NotificationTextContentLabel] = n.content
|
||||
|
||||
meta := &headerv1.Metadata{
|
||||
Labels: map[string]string{
|
||||
types.NotificationTitleLabel: n.title,
|
||||
types.NotificationTextContentLabel: n.content,
|
||||
},
|
||||
Expires: timestamppb.New(time.Now().Add(n.ttl)),
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
subKind := types.NotificationUserCreatedInformationalSubKind
|
||||
@@ -219,6 +233,11 @@ func (n *NotificationCommand) Create(ctx context.Context, client *authclient.Cli
|
||||
}
|
||||
|
||||
func (n *NotificationCommand) List(ctx context.Context, client notificationspb.NotificationServiceClient) error {
|
||||
labels, err := libclient.ParseLabelSpec(n.labels)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
var result []*notificationspb.Notification
|
||||
var pageToken string
|
||||
for {
|
||||
@@ -233,6 +252,7 @@ func (n *NotificationCommand) List(ctx context.Context, client notificationspb.N
|
||||
Filters: ¬ificationspb.NotificationFilters{
|
||||
Username: n.user,
|
||||
UserCreatedOnly: !n.allNotifications,
|
||||
Labels: labels,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -245,6 +265,7 @@ func (n *NotificationCommand) List(ctx context.Context, client notificationspb.N
|
||||
Filters: ¬ificationspb.NotificationFilters{
|
||||
GlobalOnly: true,
|
||||
UserCreatedOnly: !n.allNotifications,
|
||||
Labels: labels,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -266,13 +287,14 @@ func (n *NotificationCommand) List(ctx context.Context, client notificationspb.N
|
||||
func displayNotifications(format string, notifications []*notificationspb.Notification, w io.Writer) {
|
||||
switch format {
|
||||
case teleport.Text:
|
||||
table := asciitable.MakeTable([]string{"ID", "Created", "Expires", "Title"})
|
||||
table := asciitable.MakeTable([]string{"ID", "Created", "Expires", "Title", "Labels"})
|
||||
for _, n := range notifications {
|
||||
table.AddRow([]string{
|
||||
n.GetMetadata().GetName(),
|
||||
n.GetSpec().GetCreated().AsTime().Format(time.RFC822),
|
||||
n.GetMetadata().GetExpires().AsTime().Format(time.RFC822),
|
||||
n.GetMetadata().GetLabels()[types.NotificationTitleLabel],
|
||||
common.FormatLabels(n.GetMetadata().GetLabels(), false),
|
||||
})
|
||||
}
|
||||
fmt.Fprint(w, table.AsBuffer().String())
|
||||
|
||||
@@ -64,7 +64,11 @@ func TestNotificationCommmandCRUD(t *testing.T) {
|
||||
require.Contains(t, buf.String(), "for user manager-user")
|
||||
|
||||
// Test creating a global notification for users with the test-1 role.
|
||||
buf, err = runNotificationsCommand(t, clt, []string{"create", "--roles", "test-1", "--title", "test-1 notification", "--content", "This is a test notification."})
|
||||
buf, err = runNotificationsCommand(t, clt, []string{
|
||||
"create", "--roles", "test-1", "--title", "test-1 notification",
|
||||
"--labels", "forrole=test-1",
|
||||
"--content", "This is a test notification.",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, buf.String(), "for users with one or more of the following roles: [test-1]")
|
||||
globalNotificationId := strings.Split(buf.String(), " ")[2]
|
||||
@@ -90,6 +94,18 @@ func TestNotificationCommmandCRUD(t *testing.T) {
|
||||
assert.NotContains(collectT, buf.String(), "auditor notification")
|
||||
assert.NotContains(collectT, buf.String(), "manager notification")
|
||||
|
||||
// Filter out notifications with a non-existent label and make sure nothing comes back.
|
||||
buf, err = runNotificationsCommand(t, clt, []string{"ls", "--labels=thislabel=doesnotexist"})
|
||||
assert.NotContains(collectT, buf.String(), "test-1 notification")
|
||||
assert.NotContains(collectT, buf.String(), "auditor notification")
|
||||
assert.NotContains(collectT, buf.String(), "manager notification")
|
||||
|
||||
// Filter out global notifications with a valid label.
|
||||
buf, err = runNotificationsCommand(t, clt, []string{"ls", "--labels=forrole=test-1"})
|
||||
assert.Contains(collectT, buf.String(), "test-1 notification")
|
||||
assert.NotContains(collectT, buf.String(), "auditor notification")
|
||||
assert.NotContains(collectT, buf.String(), "manager notification")
|
||||
|
||||
}, 3*time.Second, 100*time.Millisecond)
|
||||
|
||||
// Delete the auditor's user-specific notification.
|
||||
|
||||
Reference in New Issue
Block a user