From ccd2a9915d90399c672e54c1d252a7a996b86fc2 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Fri, 13 Dec 2024 08:53:10 +0000 Subject: [PATCH] Add last state change timestamp to UserTasks (#50130) This PR adds a new field to the UserTask Status. It will register the timestamp of when the UserTask state was last changed. --- .../go/teleport/usertasks/v1/user_tasks.pb.go | 328 +++++++++++------- .../teleport/usertasks/v1/user_tasks.proto | 8 + lib/auth/usertasks/usertasksv1/service.go | 37 +- .../usertasks/usertasksv1/service_test.go | 38 +- lib/web/ui/usertask.go | 15 +- lib/web/usertasks_test.go | 4 + 6 files changed, 283 insertions(+), 147 deletions(-) diff --git a/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go b/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go index b417f2631fd6c..405295eca1aa2 100644 --- a/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go +++ b/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go @@ -56,6 +56,8 @@ type UserTask struct { Metadata *v1.Metadata `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` // The configured properties of UserTask. Spec *UserTaskSpec `protobuf:"bytes,5,opt,name=spec,proto3" json:"spec,omitempty"` + // The current status for this UserTask. + Status *UserTaskStatus `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` } func (x *UserTask) Reset() { @@ -123,6 +125,13 @@ func (x *UserTask) GetSpec() *UserTaskSpec { return nil } +func (x *UserTask) GetStatus() *UserTaskStatus { + if x != nil { + return x.Status + } + return nil +} + // UserTaskSpec contains the properties of the UserTask. type UserTaskSpec struct { state protoimpl.MessageState @@ -222,6 +231,53 @@ func (x *UserTaskSpec) GetDiscoverEks() *DiscoverEKS { return nil } +// UserTaskStatus contains the current status for the UserTask. +type UserTaskStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // LastStateChange is the timestamp when the UserTask state was last modified. + LastStateChange *timestamppb.Timestamp `protobuf:"bytes,1,opt,name=last_state_change,json=lastStateChange,proto3" json:"last_state_change,omitempty"` +} + +func (x *UserTaskStatus) Reset() { + *x = UserTaskStatus{} + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserTaskStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserTaskStatus) ProtoMessage() {} + +func (x *UserTaskStatus) ProtoReflect() protoreflect.Message { + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UserTaskStatus.ProtoReflect.Descriptor instead. +func (*UserTaskStatus) Descriptor() ([]byte, []int) { + return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{2} +} + +func (x *UserTaskStatus) GetLastStateChange() *timestamppb.Timestamp { + if x != nil { + return x.LastStateChange + } + return nil +} + // DiscoverEC2 contains the instances that failed to auto-enroll into the cluster. type DiscoverEC2 struct { state protoimpl.MessageState @@ -244,7 +300,7 @@ type DiscoverEC2 struct { func (x *DiscoverEC2) Reset() { *x = DiscoverEC2{} - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[2] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -256,7 +312,7 @@ func (x *DiscoverEC2) String() string { func (*DiscoverEC2) ProtoMessage() {} func (x *DiscoverEC2) ProtoReflect() protoreflect.Message { - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[2] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -269,7 +325,7 @@ func (x *DiscoverEC2) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverEC2.ProtoReflect.Descriptor instead. func (*DiscoverEC2) Descriptor() ([]byte, []int) { - return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{2} + return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{3} } func (x *DiscoverEC2) GetInstances() map[string]*DiscoverEC2Instance { @@ -331,7 +387,7 @@ type DiscoverEC2Instance struct { func (x *DiscoverEC2Instance) Reset() { *x = DiscoverEC2Instance{} - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[3] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -343,7 +399,7 @@ func (x *DiscoverEC2Instance) String() string { func (*DiscoverEC2Instance) ProtoMessage() {} func (x *DiscoverEC2Instance) ProtoReflect() protoreflect.Message { - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[3] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -356,7 +412,7 @@ func (x *DiscoverEC2Instance) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverEC2Instance.ProtoReflect.Descriptor instead. func (*DiscoverEC2Instance) Descriptor() ([]byte, []int) { - return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{3} + return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{4} } func (x *DiscoverEC2Instance) GetInstanceId() string { @@ -419,7 +475,7 @@ type DiscoverEKS struct { func (x *DiscoverEKS) Reset() { *x = DiscoverEKS{} - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[4] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -431,7 +487,7 @@ func (x *DiscoverEKS) String() string { func (*DiscoverEKS) ProtoMessage() {} func (x *DiscoverEKS) ProtoReflect() protoreflect.Message { - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[4] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -444,7 +500,7 @@ func (x *DiscoverEKS) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverEKS.ProtoReflect.Descriptor instead. func (*DiscoverEKS) Descriptor() ([]byte, []int) { - return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{4} + return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{5} } func (x *DiscoverEKS) GetClusters() map[string]*DiscoverEKSCluster { @@ -493,7 +549,7 @@ type DiscoverEKSCluster struct { func (x *DiscoverEKSCluster) Reset() { *x = DiscoverEKSCluster{} - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[5] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -505,7 +561,7 @@ func (x *DiscoverEKSCluster) String() string { func (*DiscoverEKSCluster) ProtoMessage() {} func (x *DiscoverEKSCluster) ProtoReflect() protoreflect.Message { - mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[5] + mi := &file_teleport_usertasks_v1_user_tasks_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -518,7 +574,7 @@ func (x *DiscoverEKSCluster) ProtoReflect() protoreflect.Message { // Deprecated: Use DiscoverEKSCluster.ProtoReflect.Descriptor instead. func (*DiscoverEKSCluster) Descriptor() ([]byte, []int) { - return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{5} + return file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP(), []int{6} } func (x *DiscoverEKSCluster) GetName() string { @@ -560,7 +616,7 @@ var file_teleport_usertasks_v1_user_tasks_proto_rawDesc = []byte{ 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x01, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, + 0x6f, 0x74, 0x6f, 0x22, 0x85, 0x02, 0x0a, 0x08, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x75, 0x62, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x4b, 0x69, 0x6e, 0x64, 0x12, @@ -572,100 +628,109 @@ var file_teleport_usertasks_v1_user_tasks_proto_rawDesc = []byte{ 0x61, 0x74, 0x61, 0x12, 0x37, 0x0a, 0x04, 0x73, 0x70, 0x65, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, - 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x22, 0x90, 0x02, 0x0a, - 0x0c, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x20, 0x0a, - 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, - 0x74, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x12, 0x45, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x65, 0x63, - 0x32, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x52, 0x0b, 0x64, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x63, 0x32, 0x12, 0x45, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x65, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, - 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, - 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, - 0x4b, 0x53, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x6b, 0x73, 0x22, - 0xcd, 0x02, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x12, - 0x4f, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, - 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, - 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x6d, 0x5f, 0x64, - 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, - 0x73, 0x6d, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x53, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x1a, 0x68, 0x0a, 0x0e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 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, 0x40, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x9e, 0x02, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, - 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x55, 0x72, 0x6c, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, - 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, - 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, - 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x67, 0x72, 0x6f, 0x75, - 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, - 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, - 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, - 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x52, 0x0a, 0x61, 0x63, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, - 0x22, 0xa6, 0x02, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, - 0x12, 0x4c, 0x0a, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1d, - 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, - 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x61, 0x75, 0x74, - 0x6f, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0f, 0x61, 0x70, 0x70, 0x41, 0x75, 0x74, 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x1a, 0x66, 0x0a, 0x0d, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 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, 0x3f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, - 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, - 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb5, 0x01, 0x0a, 0x12, 0x44, 0x69, - 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, - 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, - 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x52, 0x04, 0x73, 0x70, 0x65, 0x63, 0x12, 0x3d, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x90, 0x02, 0x0a, 0x0c, + 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x70, 0x65, 0x63, 0x12, 0x20, 0x0a, 0x0b, + 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, + 0x0a, 0x09, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x74, 0x61, 0x73, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x69, + 0x73, 0x73, 0x75, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x69, 0x73, 0x73, 0x75, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x45, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x65, 0x63, 0x32, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, + 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x45, 0x63, 0x32, 0x12, 0x45, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x5f, 0x65, 0x6b, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, + 0x53, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x6b, 0x73, 0x22, 0x58, + 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x12, 0x46, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, - 0x65, 0x42, 0x56, 0x5a, 0x54, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2f, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, - 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6c, 0x61, 0x73, 0x74, 0x53, 0x74, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0xcd, 0x02, 0x0a, 0x0b, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x12, 0x4f, 0x0a, 0x09, 0x69, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x2e, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, + 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, + 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x73, 0x6d, 0x5f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x73, 0x6d, 0x44, 0x6f, 0x63, 0x75, 0x6d, + 0x65, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, + 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x1a, 0x68, + 0x0a, 0x0e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 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, 0x40, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, + 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9e, 0x02, 0x0a, 0x13, 0x44, 0x69, 0x73, + 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, + 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, + 0x6e, 0x76, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x72, 0x6c, 0x12, 0x29, 0x0a, 0x10, + 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x79, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x22, 0xa6, 0x02, 0x0a, 0x0b, 0x44, 0x69, + 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, 0x12, 0x4c, 0x0a, 0x08, 0x63, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, 0x2e, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x63, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x12, 0x2a, + 0x0a, 0x11, 0x61, 0x70, 0x70, 0x5f, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x6f, + 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x61, 0x70, 0x70, 0x41, 0x75, + 0x74, 0x6f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x1a, 0x66, 0x0a, 0x0d, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 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, 0x3f, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x4b, 0x53, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0xb5, 0x01, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, + 0x4b, 0x53, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, + 0x10, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, + 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x63, + 0x6f, 0x76, 0x65, 0x72, 0x79, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x47, 0x72, 0x6f, 0x75, + 0x70, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x56, 0x5a, 0x54, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, + 0x73, 0x6b, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -680,35 +745,38 @@ func file_teleport_usertasks_v1_user_tasks_proto_rawDescGZIP() []byte { return file_teleport_usertasks_v1_user_tasks_proto_rawDescData } -var file_teleport_usertasks_v1_user_tasks_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_teleport_usertasks_v1_user_tasks_proto_msgTypes = make([]protoimpl.MessageInfo, 9) var file_teleport_usertasks_v1_user_tasks_proto_goTypes = []any{ (*UserTask)(nil), // 0: teleport.usertasks.v1.UserTask (*UserTaskSpec)(nil), // 1: teleport.usertasks.v1.UserTaskSpec - (*DiscoverEC2)(nil), // 2: teleport.usertasks.v1.DiscoverEC2 - (*DiscoverEC2Instance)(nil), // 3: teleport.usertasks.v1.DiscoverEC2Instance - (*DiscoverEKS)(nil), // 4: teleport.usertasks.v1.DiscoverEKS - (*DiscoverEKSCluster)(nil), // 5: teleport.usertasks.v1.DiscoverEKSCluster - nil, // 6: teleport.usertasks.v1.DiscoverEC2.InstancesEntry - nil, // 7: teleport.usertasks.v1.DiscoverEKS.ClustersEntry - (*v1.Metadata)(nil), // 8: teleport.header.v1.Metadata - (*timestamppb.Timestamp)(nil), // 9: google.protobuf.Timestamp + (*UserTaskStatus)(nil), // 2: teleport.usertasks.v1.UserTaskStatus + (*DiscoverEC2)(nil), // 3: teleport.usertasks.v1.DiscoverEC2 + (*DiscoverEC2Instance)(nil), // 4: teleport.usertasks.v1.DiscoverEC2Instance + (*DiscoverEKS)(nil), // 5: teleport.usertasks.v1.DiscoverEKS + (*DiscoverEKSCluster)(nil), // 6: teleport.usertasks.v1.DiscoverEKSCluster + nil, // 7: teleport.usertasks.v1.DiscoverEC2.InstancesEntry + nil, // 8: teleport.usertasks.v1.DiscoverEKS.ClustersEntry + (*v1.Metadata)(nil), // 9: teleport.header.v1.Metadata + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp } var file_teleport_usertasks_v1_user_tasks_proto_depIdxs = []int32{ - 8, // 0: teleport.usertasks.v1.UserTask.metadata:type_name -> teleport.header.v1.Metadata + 9, // 0: teleport.usertasks.v1.UserTask.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.usertasks.v1.UserTask.spec:type_name -> teleport.usertasks.v1.UserTaskSpec - 2, // 2: teleport.usertasks.v1.UserTaskSpec.discover_ec2:type_name -> teleport.usertasks.v1.DiscoverEC2 - 4, // 3: teleport.usertasks.v1.UserTaskSpec.discover_eks:type_name -> teleport.usertasks.v1.DiscoverEKS - 6, // 4: teleport.usertasks.v1.DiscoverEC2.instances:type_name -> teleport.usertasks.v1.DiscoverEC2.InstancesEntry - 9, // 5: teleport.usertasks.v1.DiscoverEC2Instance.sync_time:type_name -> google.protobuf.Timestamp - 7, // 6: teleport.usertasks.v1.DiscoverEKS.clusters:type_name -> teleport.usertasks.v1.DiscoverEKS.ClustersEntry - 9, // 7: teleport.usertasks.v1.DiscoverEKSCluster.sync_time:type_name -> google.protobuf.Timestamp - 3, // 8: teleport.usertasks.v1.DiscoverEC2.InstancesEntry.value:type_name -> teleport.usertasks.v1.DiscoverEC2Instance - 5, // 9: teleport.usertasks.v1.DiscoverEKS.ClustersEntry.value:type_name -> teleport.usertasks.v1.DiscoverEKSCluster - 10, // [10:10] is the sub-list for method output_type - 10, // [10:10] is the sub-list for method input_type - 10, // [10:10] is the sub-list for extension type_name - 10, // [10:10] is the sub-list for extension extendee - 0, // [0:10] is the sub-list for field type_name + 2, // 2: teleport.usertasks.v1.UserTask.status:type_name -> teleport.usertasks.v1.UserTaskStatus + 3, // 3: teleport.usertasks.v1.UserTaskSpec.discover_ec2:type_name -> teleport.usertasks.v1.DiscoverEC2 + 5, // 4: teleport.usertasks.v1.UserTaskSpec.discover_eks:type_name -> teleport.usertasks.v1.DiscoverEKS + 10, // 5: teleport.usertasks.v1.UserTaskStatus.last_state_change:type_name -> google.protobuf.Timestamp + 7, // 6: teleport.usertasks.v1.DiscoverEC2.instances:type_name -> teleport.usertasks.v1.DiscoverEC2.InstancesEntry + 10, // 7: teleport.usertasks.v1.DiscoverEC2Instance.sync_time:type_name -> google.protobuf.Timestamp + 8, // 8: teleport.usertasks.v1.DiscoverEKS.clusters:type_name -> teleport.usertasks.v1.DiscoverEKS.ClustersEntry + 10, // 9: teleport.usertasks.v1.DiscoverEKSCluster.sync_time:type_name -> google.protobuf.Timestamp + 4, // 10: teleport.usertasks.v1.DiscoverEC2.InstancesEntry.value:type_name -> teleport.usertasks.v1.DiscoverEC2Instance + 6, // 11: teleport.usertasks.v1.DiscoverEKS.ClustersEntry.value:type_name -> teleport.usertasks.v1.DiscoverEKSCluster + 12, // [12:12] is the sub-list for method output_type + 12, // [12:12] is the sub-list for method input_type + 12, // [12:12] is the sub-list for extension type_name + 12, // [12:12] is the sub-list for extension extendee + 0, // [0:12] is the sub-list for field type_name } func init() { file_teleport_usertasks_v1_user_tasks_proto_init() } @@ -722,7 +790,7 @@ func file_teleport_usertasks_v1_user_tasks_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_teleport_usertasks_v1_user_tasks_proto_rawDesc, NumEnums: 0, - NumMessages: 8, + NumMessages: 9, NumExtensions: 0, NumServices: 0, }, diff --git a/api/proto/teleport/usertasks/v1/user_tasks.proto b/api/proto/teleport/usertasks/v1/user_tasks.proto index 9ea33d81c54cf..84f3673c7ba1c 100644 --- a/api/proto/teleport/usertasks/v1/user_tasks.proto +++ b/api/proto/teleport/usertasks/v1/user_tasks.proto @@ -37,6 +37,8 @@ message UserTask { teleport.header.v1.Metadata metadata = 4; // The configured properties of UserTask. UserTaskSpec spec = 5; + // The current status for this UserTask. + UserTaskStatus status = 6; } // UserTaskSpec contains the properties of the UserTask. @@ -62,6 +64,12 @@ message UserTaskSpec { DiscoverEKS discover_eks = 6; } +// UserTaskStatus contains the current status for the UserTask. +message UserTaskStatus { + // LastStateChange is the timestamp when the UserTask state was last modified. + google.protobuf.Timestamp last_state_change = 1; +} + // DiscoverEC2 contains the instances that failed to auto-enroll into the cluster. message DiscoverEC2 { // Instances maps an instance id to the result of enrolling that instance into teleport. diff --git a/lib/auth/usertasks/usertasksv1/service.go b/lib/auth/usertasks/usertasksv1/service.go index 5a1e388150dd2..cf94dd57ac447 100644 --- a/lib/auth/usertasks/usertasksv1/service.go +++ b/lib/auth/usertasks/usertasksv1/service.go @@ -24,6 +24,7 @@ import ( "time" "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/timestamppb" @@ -48,6 +49,9 @@ type ServiceConfig struct { // Cache is the cache for storing UserTask. Cache Reader + // Clock is used to control time - mainly used for testing. + Clock clockwork.Clock + // UsageReporter is the reporter for sending usage without it be related to an API call. UsageReporter func() usagereporter.UsageReporter @@ -74,6 +78,9 @@ func (s *ServiceConfig) CheckAndSetDefaults() error { if s.Emitter == nil { return trace.BadParameter("emitter is required") } + if s.Clock == nil { + s.Clock = clockwork.NewRealClock() + } return nil } @@ -92,6 +99,7 @@ type Service struct { authorizer authz.Authorizer backend services.UserTasks cache Reader + clock clockwork.Clock usageReporter func() usagereporter.UsageReporter emitter apievents.Emitter } @@ -106,6 +114,7 @@ func NewService(cfg ServiceConfig) (*Service, error) { authorizer: cfg.Authorizer, backend: cfg.Backend, cache: cfg.Cache, + clock: cfg.Clock, usageReporter: cfg.UsageReporter, emitter: cfg.Emitter, }, nil @@ -122,6 +131,8 @@ func (s *Service) CreateUserTask(ctx context.Context, req *usertasksv1.CreateUse return nil, trace.Wrap(err) } + s.updateStatus(req.UserTask) + rsp, err := s.backend.CreateUserTask(ctx, req.UserTask) s.emitCreateAuditEvent(ctx, rsp, authCtx, err) if err != nil { @@ -249,13 +260,19 @@ func (s *Service) UpdateUserTask(ctx context.Context, req *usertasksv1.UpdateUse return nil, trace.Wrap(err) } + stateChanged := existingUserTask.GetSpec().GetState() != req.GetUserTask().GetSpec().GetState() + + if stateChanged { + s.updateStatus(req.UserTask) + } + rsp, err := s.backend.UpdateUserTask(ctx, req.UserTask) s.emitUpdateAuditEvent(ctx, existingUserTask, req.GetUserTask(), authCtx, err) if err != nil { return nil, trace.Wrap(err) } - if existingUserTask.GetSpec().GetState() != req.GetUserTask().GetSpec().GetState() { + if stateChanged { s.usageReporter().AnonymizeAndSubmit(userTaskToUserTaskStateEvent(req.GetUserTask())) } @@ -299,18 +316,22 @@ func (s *Service) UpsertUserTask(ctx context.Context, req *usertasksv1.UpsertUse return nil, trace.Wrap(err) } - var emitStateChangeEvent bool + var stateChanged bool existingUserTask, err := s.backend.GetUserTask(ctx, req.GetUserTask().GetMetadata().GetName()) switch { case trace.IsNotFound(err): - emitStateChangeEvent = true + stateChanged = true case err != nil: return nil, trace.Wrap(err) default: - emitStateChangeEvent = existingUserTask.GetSpec().GetState() != req.GetUserTask().GetSpec().GetState() + stateChanged = existingUserTask.GetSpec().GetState() != req.GetUserTask().GetSpec().GetState() + } + + if stateChanged { + s.updateStatus(req.UserTask) } rsp, err := s.backend.UpsertUserTask(ctx, req.UserTask) @@ -319,13 +340,19 @@ func (s *Service) UpsertUserTask(ctx context.Context, req *usertasksv1.UpsertUse return nil, trace.Wrap(err) } - if emitStateChangeEvent { + if stateChanged { s.usageReporter().AnonymizeAndSubmit(userTaskToUserTaskStateEvent(req.GetUserTask())) } return rsp, nil } +func (s *Service) updateStatus(ut *usertasksv1.UserTask) { + ut.Status = &usertasksv1.UserTaskStatus{ + LastStateChange: timestamppb.New(s.clock.Now()), + } +} + func (s *Service) emitUpsertAuditEvent(ctx context.Context, old, new *usertasksv1.UserTask, authCtx *authz.Context, err error) { if old == nil { s.emitCreateAuditEvent(ctx, new, authCtx, err) diff --git a/lib/auth/usertasks/usertasksv1/service_test.go b/lib/auth/usertasks/usertasksv1/service_test.go index 0f0a56fb4ef1c..d40b3740af591 100644 --- a/lib/auth/usertasks/usertasksv1/service_test.go +++ b/lib/auth/usertasks/usertasksv1/service_test.go @@ -28,7 +28,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/timestamppb" usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" "github.com/gravitational/teleport/api/types" @@ -88,7 +90,7 @@ func TestServiceAccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { for _, verbs := range utils.Combinations(tt.allowedVerbs) { t.Run(fmt.Sprintf("verbs=%v", verbs), func(t *testing.T) { - service := newService(t, fakeChecker{allowedVerbs: verbs}, testReporter, &libevents.DiscardEmitter{}) + service := newService(t, fakeChecker{allowedVerbs: verbs}, testReporter, &libevents.DiscardEmitter{}, clockwork.NewFakeClock()) err := callMethod(t, service, tt.name) // expect access denied except with full set of verbs. if len(verbs) == len(tt.allowedVerbs) { @@ -119,7 +121,8 @@ func TestEvents(t *testing.T) { rwVerbs := []string{types.VerbList, types.VerbCreate, types.VerbRead, types.VerbUpdate, types.VerbDelete} testReporter := &mockUsageReporter{} auditEventsSink := eventstest.NewChannelEmitter(10) - service := newService(t, fakeChecker{allowedVerbs: rwVerbs}, testReporter, auditEventsSink) + fakeClock := clockwork.NewFakeClock() + service := newService(t, fakeChecker{allowedVerbs: rwVerbs}, testReporter, auditEventsSink, fakeClock) ctx := context.Background() ut1, err := usertasks.NewDiscoverEC2UserTask(&usertasksv1.UserTaskSpec{ @@ -142,29 +145,37 @@ func TestEvents(t *testing.T) { require.NoError(t, err) userTaskName := ut1.GetMetadata().GetName() - _, err = service.CreateUserTask(ctx, &usertasksv1.CreateUserTaskRequest{UserTask: ut1}) + createUserTaskResp, err := service.CreateUserTask(ctx, &usertasksv1.CreateUserTaskRequest{UserTask: ut1}) require.NoError(t, err) // Usage reporting happens when user task is created, so we expect to see an event. require.Len(t, testReporter.emittedEvents, 1) consumeAssertEvent(t, auditEventsSink.C(), auditEventFor(userTaskName, "create", "", "")) + // LastStateChange is updated. + require.Equal(t, timestamppb.New(fakeClock.Now()), createUserTaskResp.Status.LastStateChange) ut1.Spec.DiscoverEc2.Instances["i-345"] = &usertasksv1.DiscoverEC2Instance{ InstanceId: "i-345", DiscoveryConfig: "dc01", DiscoveryGroup: "dg01", } - _, err = service.UpsertUserTask(ctx, &usertasksv1.UpsertUserTaskRequest{UserTask: ut1}) + fakeClock.Advance(1 * time.Minute) + upsertUserTaskResp, err := service.UpsertUserTask(ctx, &usertasksv1.UpsertUserTaskRequest{UserTask: ut1}) require.NoError(t, err) // State was not updated, so usage events must not increase. require.Len(t, testReporter.emittedEvents, 1) consumeAssertEvent(t, auditEventsSink.C(), auditEventFor(userTaskName, "update", "OPEN", "OPEN")) + // LastStateChange is not updated. + require.Equal(t, createUserTaskResp.Status.LastStateChange, upsertUserTaskResp.Status.LastStateChange) ut1.Spec.State = "RESOLVED" - _, err = service.UpdateUserTask(ctx, &usertasksv1.UpdateUserTaskRequest{UserTask: ut1}) + fakeClock.Advance(1 * time.Minute) + updateUserTaskResp, err := service.UpdateUserTask(ctx, &usertasksv1.UpdateUserTaskRequest{UserTask: ut1}) require.NoError(t, err) // State was updated, so usage events include this new usage report. require.Len(t, testReporter.emittedEvents, 2) consumeAssertEvent(t, auditEventsSink.C(), auditEventFor(userTaskName, "update", "OPEN", "RESOLVED")) + // LastStateChange was updated because the state changed. + require.Equal(t, timestamppb.New(fakeClock.Now()), updateUserTaskResp.Status.LastStateChange) _, err = service.DeleteUserTask(ctx, &usertasksv1.DeleteUserTaskRequest{Name: userTaskName}) require.NoError(t, err) @@ -241,9 +252,21 @@ func consumeAssertEvent(t *testing.T, q <-chan apievents.AuditEvent, expectedEve // callMethod calls a method with given name in the UserTask service func callMethod(t *testing.T, service *Service, method string) error { + emptyUserTask := &usertasksv1.UserTask{ + Spec: &usertasksv1.UserTaskSpec{}, + } + for _, desc := range usertasksv1.UserTaskService_ServiceDesc.Methods { if desc.MethodName == method { - _, err := desc.Handler(service, context.Background(), func(_ any) error { return nil }, nil) + _, err := desc.Handler(service, context.Background(), func(arg any) error { + switch arg := arg.(type) { + case *usertasksv1.CreateUserTaskRequest: + arg.UserTask = emptyUserTask + case *usertasksv1.UpsertUserTaskRequest: + arg.UserTask = emptyUserTask + } + return nil + }, nil) return err } } @@ -266,7 +289,7 @@ func (f fakeChecker) CheckAccessToRule(_ services.RuleContext, _ string, resourc return trace.AccessDenied("access denied to rule=%v/verb=%v", resource, verb) } -func newService(t *testing.T, checker services.AccessChecker, usageReporter usagereporter.UsageReporter, emitter apievents.Emitter) *Service { +func newService(t *testing.T, checker services.AccessChecker, usageReporter usagereporter.UsageReporter, emitter apievents.Emitter, clock clockwork.Clock) *Service { t.Helper() b, err := memory.New(memory.Config{}) @@ -297,6 +320,7 @@ func newService(t *testing.T, checker services.AccessChecker, usageReporter usag Cache: backendService, UsageReporter: func() usagereporter.UsageReporter { return usageReporter }, Emitter: emitter, + Clock: clock, }) require.NoError(t, err) return service diff --git a/lib/web/ui/usertask.go b/lib/web/ui/usertask.go index 603867a66b017..f6d9835f96dfc 100644 --- a/lib/web/ui/usertask.go +++ b/lib/web/ui/usertask.go @@ -19,6 +19,8 @@ package ui import ( + "time" + "github.com/gravitational/trace" usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" @@ -37,6 +39,8 @@ type UserTask struct { IssueType string `json:"issueType,omitempty"` // Integration is the Integration Name this User Task refers to. Integration string `json:"integration,omitempty"` + // LastStateChange indicates when the current's user task state was last changed. + LastStateChange time.Time `json:"lastStateChange,omitempty"` } // UserTaskDetail contains all the details for a User Task. @@ -94,10 +98,11 @@ func MakeDetailedUserTask(ut *usertasksv1.UserTask) UserTaskDetail { // MakeUserTask creates a UI UserTask representation. func MakeUserTask(ut *usertasksv1.UserTask) UserTask { return UserTask{ - Name: ut.GetMetadata().GetName(), - TaskType: ut.GetSpec().GetTaskType(), - State: ut.GetSpec().GetState(), - IssueType: ut.GetSpec().GetIssueType(), - Integration: ut.GetSpec().GetIntegration(), + Name: ut.GetMetadata().GetName(), + TaskType: ut.GetSpec().GetTaskType(), + State: ut.GetSpec().GetState(), + IssueType: ut.GetSpec().GetIssueType(), + Integration: ut.GetSpec().GetIntegration(), + LastStateChange: ut.GetStatus().GetLastStateChange().AsTime(), } } diff --git a/lib/web/usertasks_test.go b/lib/web/usertasks_test.go index 79a8a553e799d..0bb2dbb9a9f9a 100644 --- a/lib/web/usertasks_test.go +++ b/lib/web/usertasks_test.go @@ -139,6 +139,7 @@ func TestUserTask(t *testing.T) { require.NoError(t, err) require.Equal(t, "OPEN", userTaskDetailResp.State) require.NotEmpty(t, userTaskDetailResp.DiscoverEC2) + lastStateChangeT0 := userTaskDetailResp.LastStateChange // Mark it as resolved. _, err = pack.clt.PutJSON(ctx, updateStateEndpoint(userTaskName), ui.UpdateUserTaskStateRequest{ @@ -153,5 +154,8 @@ func TestUserTask(t *testing.T) { require.NoError(t, err) require.NotEmpty(t, userTaskDetailResp.DiscoverEC2) require.Equal(t, "RESOLVED", userTaskDetailResp.State) + // Its last changed state should be updated. + lastStateChangeT1 := userTaskDetailResp.LastStateChange + require.True(t, lastStateChangeT1.After(lastStateChangeT0), "last state change was not updated after changing the UserTask state") }) }