From e6b8f7381960c6e6cb04cc81429e7609d4c53c25 Mon Sep 17 00:00:00 2001 From: Yihong Wang Date: Tue, 2 Jul 2024 17:28:49 -0700 Subject: [PATCH] Implement an RBAC-free machanism in the driver Create a gRPC server in the controller to receive the status updates from the driver to avoid the RBAC for the driver Pod. - Add gRPC server in the controller - Use mTLS or TLS as the secured protocol - Update the driver to use the gRPC client to update the status Signed-off-by: Yihong Wang --- Makefile | 5 + backend/api/v1beta1/update_status.pb.go | 344 ++++++++++++++++++ backend/api/v1beta1/update_status.proto | 41 +++ backend/api/v1beta1/update_status_grpc.pb.go | 120 +++++++ backend/controller/grpc_server.go | 119 +++++++ backend/controller/grpc_server_test.go | 138 ++++++++ backend/controller/lmevaljob_controller.go | 348 +++++++++++++++---- backend/driver/driver.go | 249 ++++++++----- backend/driver/driver_test.go | 3 +- cmd/controller/main.go | 8 +- cmd/driver/main.go | 14 +- config/certmanager/certificate.yaml | 42 +++ config/default/kustomization.yaml | 62 ++++ config/default/manager_webhook_patch.yaml | 21 ++ config/manager/configmap.yaml | 2 + config/manager/kustomization.yaml | 1 + config/manager/manager.yaml | 2 +- config/manager/service.yaml | 14 + config/rbac/driver_role.yaml | 38 -- config/rbac/driver_role_binding.yaml | 14 - config/rbac/kustomization.yaml | 2 - config/rbac/role.yaml | 8 + docker/Dockerfile | 2 +- go.mod | 38 +- go.sum | 103 ++++-- 25 files changed, 1497 insertions(+), 241 deletions(-) create mode 100644 backend/api/v1beta1/update_status.pb.go create mode 100644 backend/api/v1beta1/update_status.proto create mode 100644 backend/api/v1beta1/update_status_grpc.pb.go create mode 100644 backend/controller/grpc_server.go create mode 100644 backend/controller/grpc_server_test.go create mode 100644 config/manager/service.yaml delete mode 100644 config/rbac/driver_role.yaml delete mode 100644 config/rbac/driver_role_binding.yaml diff --git a/Makefile b/Makefile index 9f9380a..3d11998 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,11 @@ docker-push: ## Push docker image with the manager. docker-push-driver: ## Push docker image with the manager. $(CONTAINER_TOOL) push ${IMG_DRIVER} +.PHONY: proto +proto: ## generate GRPC client and server code + protoc --experimental_allow_proto3_optional --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \ + --go-grpc_opt=paths=source_relative backend/api/v1beta1/update_status.proto + # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ diff --git a/backend/api/v1beta1/update_status.pb.go b/backend/api/v1beta1/update_status.pb.go new file mode 100644 index 0000000..cf83b94 --- /dev/null +++ b/backend/api/v1beta1/update_status.pb.go @@ -0,0 +1,344 @@ +// Copyright 2024. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.34.2 +// protoc v3.12.4 +// source: backend/api/v1beta1/update_status.proto + +package v1beta1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ResponseCode int32 + +const ( + ResponseCode_OK ResponseCode = 0 + ResponseCode_ERROR ResponseCode = 1 +) + +// Enum value maps for ResponseCode. +var ( + ResponseCode_name = map[int32]string{ + 0: "OK", + 1: "ERROR", + } + ResponseCode_value = map[string]int32{ + "OK": 0, + "ERROR": 1, + } +) + +func (x ResponseCode) Enum() *ResponseCode { + p := new(ResponseCode) + *p = x + return p +} + +func (x ResponseCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResponseCode) Descriptor() protoreflect.EnumDescriptor { + return file_backend_api_v1beta1_update_status_proto_enumTypes[0].Descriptor() +} + +func (ResponseCode) Type() protoreflect.EnumType { + return &file_backend_api_v1beta1_update_status_proto_enumTypes[0] +} + +func (x ResponseCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResponseCode.Descriptor instead. +func (ResponseCode) EnumDescriptor() ([]byte, []int) { + return file_backend_api_v1beta1_update_status_proto_rawDescGZIP(), []int{0} +} + +// the JobState, Reason, message, and optional Results +type JobStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + JobName string `protobuf:"bytes,1,opt,name=job_name,json=jobName,proto3" json:"job_name,omitempty"` + JobNamespace string `protobuf:"bytes,2,opt,name=job_namespace,json=jobNamespace,proto3" json:"job_namespace,omitempty"` + State string `protobuf:"bytes,3,opt,name=state,proto3" json:"state,omitempty"` + Reason string `protobuf:"bytes,4,opt,name=reason,proto3" json:"reason,omitempty"` + StatusMessage string `protobuf:"bytes,5,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` + Results *string `protobuf:"bytes,6,opt,name=results,proto3,oneof" json:"results,omitempty"` +} + +func (x *JobStatus) Reset() { + *x = JobStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_api_v1beta1_update_status_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *JobStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JobStatus) ProtoMessage() {} + +func (x *JobStatus) ProtoReflect() protoreflect.Message { + mi := &file_backend_api_v1beta1_update_status_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JobStatus.ProtoReflect.Descriptor instead. +func (*JobStatus) Descriptor() ([]byte, []int) { + return file_backend_api_v1beta1_update_status_proto_rawDescGZIP(), []int{0} +} + +func (x *JobStatus) GetJobName() string { + if x != nil { + return x.JobName + } + return "" +} + +func (x *JobStatus) GetJobNamespace() string { + if x != nil { + return x.JobNamespace + } + return "" +} + +func (x *JobStatus) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *JobStatus) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +func (x *JobStatus) GetStatusMessage() string { + if x != nil { + return x.StatusMessage + } + return "" +} + +func (x *JobStatus) GetResults() string { + if x != nil && x.Results != nil { + return *x.Results + } + return "" +} + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code ResponseCode `protobuf:"varint,1,opt,name=code,proto3,enum=ResponseCode" json:"code,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_backend_api_v1beta1_update_status_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_backend_api_v1beta1_update_status_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_backend_api_v1beta1_update_status_proto_rawDescGZIP(), []int{1} +} + +func (x *Response) GetCode() ResponseCode { + if x != nil { + return x.Code + } + return ResponseCode_OK +} + +func (x *Response) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_backend_api_v1beta1_update_status_proto protoreflect.FileDescriptor + +var file_backend_api_v1beta1_update_status_proto_rawDesc = []byte{ + 0x0a, 0x27, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xcb, 0x01, 0x0a, 0x09, 0x4a, 0x6f, + 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x6f, 0x62, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6a, 0x6f, 0x62, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6a, 0x6f, 0x62, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, + 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x0a, 0x07, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, + 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0x47, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x0d, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x2a, 0x21, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x01, 0x32, 0x3f, 0x0a, 0x16, 0x4c, 0x4d, 0x45, 0x76, 0x61, 0x6c, 0x4a, 0x6f, 0x62, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x25, 0x0a, + 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0a, 0x2e, + 0x4a, 0x6f, 0x62, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x09, 0x2e, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4b, 0x5a, 0x49, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x2d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x2f, 0x66, 0x6d, 0x73, 0x2d, 0x6c, 0x6d, + 0x2d, 0x65, 0x76, 0x61, 0x6c, 0x2d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2f, 0x62, 0x61, + 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_backend_api_v1beta1_update_status_proto_rawDescOnce sync.Once + file_backend_api_v1beta1_update_status_proto_rawDescData = file_backend_api_v1beta1_update_status_proto_rawDesc +) + +func file_backend_api_v1beta1_update_status_proto_rawDescGZIP() []byte { + file_backend_api_v1beta1_update_status_proto_rawDescOnce.Do(func() { + file_backend_api_v1beta1_update_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_api_v1beta1_update_status_proto_rawDescData) + }) + return file_backend_api_v1beta1_update_status_proto_rawDescData +} + +var file_backend_api_v1beta1_update_status_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_backend_api_v1beta1_update_status_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_backend_api_v1beta1_update_status_proto_goTypes = []any{ + (ResponseCode)(0), // 0: ResponseCode + (*JobStatus)(nil), // 1: JobStatus + (*Response)(nil), // 2: Response +} +var file_backend_api_v1beta1_update_status_proto_depIdxs = []int32{ + 0, // 0: Response.code:type_name -> ResponseCode + 1, // 1: LMEvalJobUpdateService.UpdateStatus:input_type -> JobStatus + 2, // 2: LMEvalJobUpdateService.UpdateStatus:output_type -> Response + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_backend_api_v1beta1_update_status_proto_init() } +func file_backend_api_v1beta1_update_status_proto_init() { + if File_backend_api_v1beta1_update_status_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_backend_api_v1beta1_update_status_proto_msgTypes[0].Exporter = func(v any, i int) any { + switch v := v.(*JobStatus); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_backend_api_v1beta1_update_status_proto_msgTypes[1].Exporter = func(v any, i int) any { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_backend_api_v1beta1_update_status_proto_msgTypes[0].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_backend_api_v1beta1_update_status_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_backend_api_v1beta1_update_status_proto_goTypes, + DependencyIndexes: file_backend_api_v1beta1_update_status_proto_depIdxs, + EnumInfos: file_backend_api_v1beta1_update_status_proto_enumTypes, + MessageInfos: file_backend_api_v1beta1_update_status_proto_msgTypes, + }.Build() + File_backend_api_v1beta1_update_status_proto = out.File + file_backend_api_v1beta1_update_status_proto_rawDesc = nil + file_backend_api_v1beta1_update_status_proto_goTypes = nil + file_backend_api_v1beta1_update_status_proto_depIdxs = nil +} diff --git a/backend/api/v1beta1/update_status.proto b/backend/api/v1beta1/update_status.proto new file mode 100644 index 0000000..a7cbc01 --- /dev/null +++ b/backend/api/v1beta1/update_status.proto @@ -0,0 +1,41 @@ +// Copyright 2024. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +option go_package = "github.com/foundation-model-stack/fms-lm-eval-service/backend/api/v1beta1"; + +enum ResponseCode { + OK = 0; + ERROR = 1; +} + +// the JobState, Reason, message, and optional Results +message JobStatus { + string job_name = 1; + string job_namespace = 2; + string state = 3; + string reason = 4; + string status_message = 5; + optional string results = 6; +} + +message Response { + ResponseCode code = 1; + string message = 2; +} + +service LMEvalJobUpdateService { + rpc UpdateStatus(JobStatus) returns (Response); +} diff --git a/backend/api/v1beta1/update_status_grpc.pb.go b/backend/api/v1beta1/update_status_grpc.pb.go new file mode 100644 index 0000000..48b732a --- /dev/null +++ b/backend/api/v1beta1/update_status_grpc.pb.go @@ -0,0 +1,120 @@ +// Copyright 2024. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.12.4 +// source: backend/api/v1beta1/update_status.proto + +package v1beta1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// LMEvalJobUpdateServiceClient is the client API for LMEvalJobUpdateService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type LMEvalJobUpdateServiceClient interface { + UpdateStatus(ctx context.Context, in *JobStatus, opts ...grpc.CallOption) (*Response, error) +} + +type lMEvalJobUpdateServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewLMEvalJobUpdateServiceClient(cc grpc.ClientConnInterface) LMEvalJobUpdateServiceClient { + return &lMEvalJobUpdateServiceClient{cc} +} + +func (c *lMEvalJobUpdateServiceClient) UpdateStatus(ctx context.Context, in *JobStatus, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/LMEvalJobUpdateService/UpdateStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LMEvalJobUpdateServiceServer is the server API for LMEvalJobUpdateService service. +// All implementations must embed UnimplementedLMEvalJobUpdateServiceServer +// for forward compatibility +type LMEvalJobUpdateServiceServer interface { + UpdateStatus(context.Context, *JobStatus) (*Response, error) + mustEmbedUnimplementedLMEvalJobUpdateServiceServer() +} + +// UnimplementedLMEvalJobUpdateServiceServer must be embedded to have forward compatible implementations. +type UnimplementedLMEvalJobUpdateServiceServer struct { +} + +func (UnimplementedLMEvalJobUpdateServiceServer) UpdateStatus(context.Context, *JobStatus) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateStatus not implemented") +} +func (UnimplementedLMEvalJobUpdateServiceServer) mustEmbedUnimplementedLMEvalJobUpdateServiceServer() { +} + +// UnsafeLMEvalJobUpdateServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LMEvalJobUpdateServiceServer will +// result in compilation errors. +type UnsafeLMEvalJobUpdateServiceServer interface { + mustEmbedUnimplementedLMEvalJobUpdateServiceServer() +} + +func RegisterLMEvalJobUpdateServiceServer(s grpc.ServiceRegistrar, srv LMEvalJobUpdateServiceServer) { + s.RegisterService(&LMEvalJobUpdateService_ServiceDesc, srv) +} + +func _LMEvalJobUpdateService_UpdateStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(JobStatus) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LMEvalJobUpdateServiceServer).UpdateStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/LMEvalJobUpdateService/UpdateStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LMEvalJobUpdateServiceServer).UpdateStatus(ctx, req.(*JobStatus)) + } + return interceptor(ctx, in, info, handler) +} + +// LMEvalJobUpdateService_ServiceDesc is the grpc.ServiceDesc for LMEvalJobUpdateService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LMEvalJobUpdateService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "LMEvalJobUpdateService", + HandlerType: (*LMEvalJobUpdateServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateStatus", + Handler: _LMEvalJobUpdateService_UpdateStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "backend/api/v1beta1/update_status.proto", +} diff --git a/backend/controller/grpc_server.go b/backend/controller/grpc_server.go new file mode 100644 index 0000000..a6c7a5b --- /dev/null +++ b/backend/controller/grpc_server.go @@ -0,0 +1,119 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "os" + + "github.com/foundation-model-stack/fms-lm-eval-service/backend/api/v1beta1" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +var server *grpc.Server + +func StartGrpcServer(ctx context.Context, ctor *LMEvalJobReconciler) error { + log := log.FromContext(ctx) + // checking the cer/key envs + if viper.IsSet(GrpcServerCertEnv) && viper.IsSet(GrpcServerKeyEnv) { + serverKey, serverCert := viper.GetString(GrpcServerKeyEnv), viper.GetString(GrpcServerCertEnv) + + if viper.IsSet(GrpcClientCaEnv) { + // mTLS case + var err error + if server, err = getMTLSServer(serverCert, serverKey); err != nil { + return err + } + ctor.options.grpcTLSMode = TLSMode_mTLS + log.Info("GRPC server uses the mTLS") + } else { + // TLS case + creds, err := credentials.NewServerTLSFromFile(serverCert, serverKey) + if err != nil { + return err + } + server = grpc.NewServer(grpc.Creds(creds)) + ctor.options.grpcTLSMode = TLSMode_TLS + log.Info("GRPC server uses the TLS") + } + } else { + ctor.options.grpcTLSMode = TLSMode_None + log.Info("GRPC server uses insecure protocol") + server = grpc.NewServer() + } + + v1beta1.RegisterLMEvalJobUpdateServiceServer(server, &updateStatusServer{ + controller: ctor, + }) + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", ctor.options.GrpcPort)) + if err != nil { + return err + } + log.Info("GRPC server started") + err = server.Serve(lis) + return err +} + +func getMTLSServer(serverCert string, serverKey string) (*grpc.Server, error) { + clientCaPath := viper.GetString(GrpcClientCaEnv) + cert, err := tls.LoadX509KeyPair(serverCert, serverKey) + if err != nil { + return nil, err + } + ca := x509.NewCertPool() + caBytes, err := os.ReadFile(clientCaPath) + if err != nil { + return nil, err + } + if ok := ca.AppendCertsFromPEM(caBytes); !ok { + return nil, fmt.Errorf("failed to parse %q", clientCaPath) + } + tlsConfig := &tls.Config{ + ClientAuth: tls.RequireAndVerifyClientCert, + Certificates: []tls.Certificate{cert}, + ClientCAs: ca, + } + + return grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig))), nil +} + +type updateStatusServer struct { + v1beta1.UnimplementedLMEvalJobUpdateServiceServer + controller *LMEvalJobReconciler +} + +func (s *updateStatusServer) UpdateStatus(ctx context.Context, newStatus *v1beta1.JobStatus) (resp *v1beta1.Response, err error) { + resp = &v1beta1.Response{ + Code: v1beta1.ResponseCode_OK, + Message: "updated the job status successfully", + } + + err = s.controller.updateStatus(ctx, newStatus) + if err != nil { + resp.Code = v1beta1.ResponseCode_ERROR + resp.Message = err.Error() + } + + return resp, err +} diff --git a/backend/controller/grpc_server_test.go b/backend/controller/grpc_server_test.go new file mode 100644 index 0000000..ec4551e --- /dev/null +++ b/backend/controller/grpc_server_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2024. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/spf13/viper" +) + +var _ = Describe("GRPC Server", func() { + Context("Start the GRPC Server", func() { + + ctx := context.Background() + BeforeEach(func() { + By("create ca, key, cert for the test case") + ca := &x509.Certificate{ + SerialNumber: big.NewInt(2019), + Subject: pkix.Name{ + Organization: []string{"LM-Eval"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Jose"}, + StreetAddress: []string{"large lanaguage"}, + PostalCode: []string{"95141"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + Expect(err).NotTo(HaveOccurred()) + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey) + Expect(err).NotTo(HaveOccurred()) + caPEM := new(bytes.Buffer) + pem.Encode(caPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: caBytes, + }) + + caPrivKeyPEM := new(bytes.Buffer) + pem.Encode(caPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(caPrivKey), + }) + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"LM-Eval"}, + Country: []string{"US"}, + Province: []string{""}, + Locality: []string{"San Jose"}, + StreetAddress: []string{"large lanaguage"}, + PostalCode: []string{"95141"}, + }, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) + Expect(err).NotTo(HaveOccurred()) + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &certPrivKey.PublicKey, caPrivKey) + Expect(err).NotTo(HaveOccurred()) + + certPEM := new(bytes.Buffer) + pem.Encode(certPEM, &pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + certPrivKeyPEM := new(bytes.Buffer) + pem.Encode(certPrivKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), + }) + + var writeToFile = func(prefix string, content *bytes.Buffer, env string) { + tmpFile, err := os.CreateTemp("", prefix) + Expect(err).NotTo(HaveOccurred()) + _, err = tmpFile.Write(content.Bytes()) + Expect(err).NotTo(HaveOccurred()) + viper.Set(env, tmpFile.Name()) + } + + writeToFile("ca", caPEM, GrpcClientCaEnv) + writeToFile("cert", certPEM, GrpcServerCertEnv) + writeToFile("key", certPrivKeyPEM, GrpcServerKeyEnv) + }) + It("should successfully reconcile the resource", func() { + By("Reconciling the created resource") + controllerReconciler := &LMEvalJobReconciler{ + options: &ServiceOptions{ + GrpcPort: 8082, + GrpcService: "localhost", + }, + } + + go StartGrpcServer(ctx, controllerReconciler) + time.Sleep(time.Second * 10) + server.Stop() + }) + }) +}) diff --git a/backend/controller/lmevaljob_controller.go b/backend/controller/lmevaljob_controller.go index 88465dd..313b68f 100644 --- a/backend/controller/lmevaljob_controller.go +++ b/backend/controller/lmevaljob_controller.go @@ -19,7 +19,9 @@ package controller import ( "context" "fmt" + "reflect" "slices" + "strconv" "strings" "time" @@ -40,7 +42,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" lmevalservicev1beta1 "github.com/foundation-model-stack/fms-lm-eval-service/api/v1beta1" + backendv1beta1 "github.com/foundation-model-stack/fms-lm-eval-service/backend/api/v1beta1" + "github.com/foundation-model-stack/fms-lm-eval-service/backend/driver" "github.com/go-logr/logr" + "github.com/spf13/viper" ) const ( @@ -51,11 +56,22 @@ const ( DriverServiceAccountKey = "driver-serviceaccount" PodCheckingIntervalKey = "pod-checking-interval" ImagePullPolicyKey = "image-pull-policy" + GrpcPortKey = "grpc-port" + GrpcServiceKey = "grpc-service" + GrpcServerSecretKey = "grpc-server-secret" + GrpcClientSecretKey = "grpc-client-secret" + GrpcServerCertEnv = "GRPC_SERVER_CERT" + GrpcServerKeyEnv = "GRPC_SERVER_KEY" + GrpcClientCaEnv = "GRPC_CLIENT_CA" DefaultPodImage = "quay.io/yhwang/lm-eval-aas-flask:test" DefaultDriverImage = "quay.io/yhwang/lm-eval-aas-driver:test" DefaultDriverServiceAccount = "driver" DefaultPodCheckingInterval = time.Second * 10 DefaultImagePullPolicy = corev1.PullAlways + DefaultGrpcPort = 8082 + DefaultGrpcService = "lm-eval-grpc" + DefaultGrpcServerSecret = "grpc-server-cert" + DefaultGrpcClientSecret = "grpc-client-cert" ) var ( @@ -64,6 +80,26 @@ var ( corev1.PullNever: corev1.PullNever, corev1.PullIfNotPresent: corev1.PullIfNotPresent, } + + optionKeys = map[string]string{ + "PodImage": PodImageKey, + "DriverImage": DriverImageKey, + "DriverServiceAccount": DriverServiceAccountKey, + "PodCheckingInterval": PodCheckingIntervalKey, + "ImagePullPolicy": ImagePullPolicyKey, + "GrpcPort": GrpcPortKey, + "GrpcService": GrpcServiceKey, + "GrpcServerSecret": GrpcServerSecretKey, + "GrpcClientSecret": GrpcClientSecretKey, + } +) + +type TLSMode int + +const ( + TLSMode_None TLSMode = 0 + TLSMode_TLS TLSMode = 1 + TLSMode_mTLS TLSMode = 2 ) // LMEvalJobReconciler reconciles a LMEvalJob object @@ -73,7 +109,7 @@ type LMEvalJobReconciler struct { Recorder record.EventRecorder ConfigMap string Namespace string - options ServiceOptions + options *ServiceOptions } type ServiceOptions struct { @@ -82,6 +118,11 @@ type ServiceOptions struct { DriverServiceAccount string PodCheckingInterval time.Duration ImagePullPolicy corev1.PullPolicy + GrpcPort int + GrpcService string + GrpcServerSecret string + GrpcClientSecret string + grpcTLSMode TLSMode } // +kubebuilder:rbac:groups=foundation-model-stack.github.com.github.com,resources=lmevaljobs,verbs=get;list;watch;create;update;patch;delete @@ -89,6 +130,7 @@ type ServiceOptions struct { // +kubebuilder:rbac:groups=foundation-model-stack.github.com.github.com,resources=lmevaljobs/finalizers,verbs=update // +kubebuilder:rbac:groups="",resources=pods,verbs=get;list;watch;create;delete // +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;watch;list +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;watch;list func (r *LMEvalJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) @@ -135,10 +177,10 @@ func (r *LMEvalJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( func (r *LMEvalJobReconciler) SetupWithManager(mgr ctrl.Manager) error { // Add a runnable to retrieve the settings from the specified configmap - if err := mgr.Add(manager.RunnableFunc(func(context.Context) error { + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { var cm corev1.ConfigMap if err := r.Get( - context.Background(), + ctx, types.NamespacedName{Namespace: r.Namespace, Name: r.ConfigMap}, &cm); err != nil { @@ -150,9 +192,23 @@ func (r *LMEvalJobReconciler) SetupWithManager(mgr ctrl.Manager) error { return err } - if err := r.constructOptionsFromConfigMap(&cm); err != nil { + if err := r.constructOptionsFromConfigMap(ctx, &cm); err != nil { return err } + + if err := r.checkSecrets(ctx); err != nil { + // if mTLS/TLS is not enable, then we are good. Otherwise error out + if viper.IsSet(GrpcServerCertEnv) || + viper.IsSet(GrpcServerKeyEnv) || + viper.IsSet(GrpcClientCaEnv) { + return fmt.Errorf("TLS or mTLS is enabled for GRPC server but secrets don't exist") + } + } + + // ideally, this should be call in the main.go, but GRPC server depends on the + // constructOptionsFromConfigMap to get the settings. + go StartGrpcServer(ctx, r) + return nil })); err != nil { return err @@ -186,40 +242,120 @@ func (r *LMEvalJobReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *LMEvalJobReconciler) constructOptionsFromConfigMap(configmap *corev1.ConfigMap) error { - r.options.DriverImage = DefaultDriverImage - r.options.PodImage = DefaultPodImage - r.options.DriverServiceAccount = DefaultDriverServiceAccount - r.options.PodCheckingInterval = DefaultPodCheckingInterval - r.options.ImagePullPolicy = DefaultImagePullPolicy - log := log.FromContext(context.Background()) +func (r *LMEvalJobReconciler) checkSecrets(ctx context.Context) error { - if v, found := configmap.Data[DriverImageKey]; found { - r.options.DriverImage = v + var isSecretExists = func(name string) bool { + var secret corev1.Secret + err := r.Get(ctx, types.NamespacedName{Namespace: r.Namespace, Name: name}, &secret) + return err == nil } - if v, found := configmap.Data[PodImageKey]; found { - r.options.PodImage = v + + if !isSecretExists(r.options.GrpcServerSecret) { + return fmt.Errorf("secret %s not found", r.options.GrpcServerSecret) } - if v, found := configmap.Data[DriverServiceAccountKey]; found { - r.options.DriverServiceAccount = v + if !isSecretExists(r.options.GrpcClientSecret) { + return fmt.Errorf("secret %s not found", r.options.GrpcServerSecret) } - if v, found := configmap.Data[PodCheckingIntervalKey]; found { - if d, err := time.ParseDuration(v); err == nil { - r.options.PodCheckingInterval = d - } else { - log.Error(err, "failed to parse the configmap", PodCheckingIntervalKey, v) - } + return nil +} + +func (r *LMEvalJobReconciler) updateStatus(ctx context.Context, newStatus *backendv1beta1.JobStatus) error { + log := log.FromContext(ctx) + + if strings.Trim(newStatus.GetJobName(), " ") == "" || + strings.Trim(newStatus.GetJobNamespace(), " ") == "" { + + return fmt.Errorf("JobName or JobNameSpace is empty") + } + + job := &lmevalservicev1beta1.LMEvalJob{} + if err := r.Get(ctx, types.NamespacedName{ + Namespace: newStatus.JobNamespace, + Name: newStatus.JobName, + }, job); err != nil { + log.Info("unable to fetch LMEvalJob") + return err } - if v, found := configmap.Data[ImagePullPolicyKey]; found { - if p, found := pullPolicyMap[corev1.PullPolicy(v)]; found { - r.options.ImagePullPolicy = p - } else { - log.Error( - fmt.Errorf("invalid %s value in the configmap: %s", ImagePullPolicyKey, v), - "use the default value for the ImagePullPolicy instead", - ) + + job.Status.State = lmevalservicev1beta1.JobState(newStatus.GetState()) + job.Status.Reason = lmevalservicev1beta1.Reason(newStatus.GetReason()) + if newStatus.GetStatusMessage() != "" { + job.Status.Message = newStatus.GetStatusMessage() + } + if newStatus.Results != nil { + job.Status.Results = newStatus.GetResults() + } + + err := r.Status().Update(ctx, job) + if err != nil { + log.Error(err, "failed to update status") + } + return err +} + +func (r *LMEvalJobReconciler) constructOptionsFromConfigMap( + ctx context.Context, configmap *corev1.ConfigMap) error { + r.options = &ServiceOptions{ + DriverImage: DefaultDriverImage, + PodImage: DefaultPodImage, + DriverServiceAccount: DefaultDriverServiceAccount, + PodCheckingInterval: DefaultPodCheckingInterval, + ImagePullPolicy: DefaultImagePullPolicy, + GrpcPort: DefaultGrpcPort, + GrpcService: DefaultGrpcService, + GrpcServerSecret: DefaultGrpcServerSecret, + GrpcClientSecret: DefaultGrpcClientSecret, + } + + log := log.FromContext(ctx) + rv := reflect.ValueOf(r.options).Elem() + var msgs []string + + for idx, cap := 0, rv.NumField(); idx < cap; idx++ { + frv := rv.Field(idx) + fname := rv.Type().Field(idx).Name + configKey, ok := optionKeys[fname] + if !ok { + continue + } + + if v, found := configmap.Data[configKey]; found { + var err error + switch frv.Type().Name() { + case "string": + frv.SetString(v) + case "int": + var grpcPort int + grpcPort, err = strconv.Atoi(v) + if err == nil { + frv.SetInt(int64(grpcPort)) + } + case "Duration": + var d time.Duration + d, err = time.ParseDuration(v) + if err == nil { + frv.Set(reflect.ValueOf(d)) + } + case "PullPolicy": + if p, found := pullPolicyMap[corev1.PullPolicy(v)]; found { + frv.Set(reflect.ValueOf(p)) + } else { + err = fmt.Errorf("invalid PullPolicy") + } + default: + return fmt.Errorf("can not handle the config %v, type: %v", optionKeys[fname], frv.Type().Name()) + } + + if err != nil { + msgs = append(msgs, fmt.Sprintf("invalid setting for %v: %v, use default setting instead", optionKeys[fname], v)) + } } } + + if len(msgs) > 0 { + log.Error(fmt.Errorf("some settings in the configmap are invalid"), strings.Join(msgs, "\n")) + } + return nil } @@ -437,6 +573,117 @@ func (r *LMEvalJobReconciler) createPod(job *lmevalservicev1beta1.LMEvalJob) *co var runAsNonRootUser = true var ownerRefController = true var runAsUser int64 = 1001030000 + var secretMode int32 = 420 + + var envVars = []corev1.EnvVar{ + { + Name: "GENAI_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + Key: "key", + LocalObjectReference: corev1.LocalObjectReference{ + Name: "genai-key", + }, + }, + }, + }, + } + + var volumeMounts = []corev1.VolumeMount{ + { + Name: "shared", + MountPath: "/opt/app-root/src/bin", + }, + } + + var volumes = []corev1.Volume{ + { + Name: "shared", VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + } + + if r.options.grpcTLSMode == TLSMode_mTLS { + envVars = append(envVars, + corev1.EnvVar{ + Name: driver.GrpcClientKeyEnv, + Value: "/tmp/k8s-grpc-client/certs/tls.key", + }, + corev1.EnvVar{ + Name: driver.GrpcClientCertEnv, + Value: "/tmp/k8s-grpc-client/certs/tls.crt", + }, + corev1.EnvVar{ + Name: driver.GrpcServerCaEnv, + Value: "/tmp/k8s-grpc-server/certs/ca.crt", + }, + ) + + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: "client-cert", + MountPath: "/tmp/k8s-grpc-client/certs", + }, + corev1.VolumeMount{ + Name: "server-cert", + MountPath: "/tmp/k8s-grpc-server/certs", + }, + ) + + volumes = append(volumes, + corev1.Volume{ + Name: "client-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: r.options.GrpcClientSecret, + DefaultMode: &secretMode, + }, + }, + }, + corev1.Volume{ + Name: "server-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: r.options.GrpcServerSecret, + DefaultMode: &secretMode, + Items: []corev1.KeyToPath{ + {Key: "ca.crt", Path: "ca.crt"}, + }, + }, + }, + }, + ) + } else if r.options.grpcTLSMode == TLSMode_TLS { + envVars = append(envVars, + corev1.EnvVar{ + Name: driver.GrpcServerCaEnv, + Value: "/tmp/k8s-grpc-server/certs/ca.crt", + }, + ) + + volumeMounts = append(volumeMounts, + corev1.VolumeMount{ + Name: "server-cert", + MountPath: "/tmp/k8s-grpc-server/certs", + }, + ) + + volumes = append(volumes, + corev1.Volume{ + Name: "server-cert", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + SecretName: r.options.GrpcServerSecret, + DefaultMode: &secretMode, + Items: []corev1.KeyToPath{ + {Key: "ca.crt", Path: "ca.crt"}, + }, + }, + }, + }, + ) + } // Then compose the Pod CR pod := corev1.Pod{ @@ -489,21 +736,9 @@ func (r *LMEvalJobReconciler) createPod(job *lmevalservicev1beta1.LMEvalJob) *co Name: "main", Image: r.options.PodImage, ImagePullPolicy: r.options.ImagePullPolicy, - Env: []corev1.EnvVar{ - { - Name: "GENAI_KEY", - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - Key: "key", - LocalObjectReference: corev1.LocalObjectReference{ - Name: "genai-key", - }, - }, - }, - }, - }, - Command: generateCmd(job), - Args: generateArgs(job), + Env: envVars, + Command: r.generateCmd(job), + Args: generateArgs(job), SecurityContext: &corev1.SecurityContext{ AllowPrivilegeEscalation: &allowPrivilegeEscalation, RunAsUser: &runAsUser, @@ -513,12 +748,7 @@ func (r *LMEvalJobReconciler) createPod(job *lmevalservicev1beta1.LMEvalJob) *co }, }, }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "shared", - MountPath: "/opt/app-root/src/bin", - }, - }, + VolumeMounts: volumeMounts, }, }, SecurityContext: &corev1.PodSecurityContext{ @@ -528,14 +758,8 @@ func (r *LMEvalJobReconciler) createPod(job *lmevalservicev1beta1.LMEvalJob) *co }, }, ServiceAccountName: r.options.DriverServiceAccount, - Volumes: []corev1.Volume{ - { - Name: "shared", VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - }, - RestartPolicy: corev1.RestartPolicyNever, + Volumes: volumes, + RestartPolicy: corev1.RestartPolicyNever, }, } return &pod @@ -576,7 +800,7 @@ func generateArgs(job *lmevalservicev1beta1.LMEvalJob) []string { return []string{"sh", "-ec", strings.Join(cmds, " ")} } -func generateCmd(job *lmevalservicev1beta1.LMEvalJob) []string { +func (r *LMEvalJobReconciler) generateCmd(job *lmevalservicev1beta1.LMEvalJob) []string { if job == nil { return nil } @@ -585,6 +809,8 @@ func generateCmd(job *lmevalservicev1beta1.LMEvalJob) []string { DestDriverPath, "--job-namespace", job.Namespace, "--job-name", job.Name, + "--grpc-service", fmt.Sprintf("%s.%s.svc", r.options.GrpcService, r.Namespace), + "--grpc-port", strconv.Itoa(r.options.GrpcPort), "--output-path", "/opt/app-root/src/output", "--", } diff --git a/backend/driver/driver.go b/backend/driver/driver.go index 9dfcc99..a803146 100644 --- a/backend/driver/driver.go +++ b/backend/driver/driver.go @@ -19,26 +19,38 @@ package driver import ( "bufio" "context" + "crypto/tls" + "crypto/x509" "fmt" + "io" "io/fs" "os" "os/exec" "path/filepath" + "time" lmevalservicev1beta1 "github.com/foundation-model-stack/fms-lm-eval-service/api/v1beta1" + "github.com/foundation-model-stack/fms-lm-eval-service/backend/api/v1beta1" "github.com/go-logr/logr" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - client "sigs.k8s.io/controller-runtime/pkg/client" ) var ( scheme = runtime.NewScheme() ) +const ( + GrpcClientKeyEnv = "GRPC_CLIENT_KEY" + GrpcClientCertEnv = "GRPC_CLIENT_CERT" + GrpcServerCaEnv = "GRPC_SERVER_CA" +) + func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(lmevalservicev1beta1.AddToScheme(scheme)) @@ -48,7 +60,8 @@ type DriverOption struct { Context context.Context JobNamespace string JobName string - ConfigMap string + GrpcService string + GrpcPort int OutputPath string Logger logr.Logger Args []string @@ -56,12 +69,13 @@ type DriverOption struct { type Driver interface { Run() error + Cleanup() } type driverImpl struct { - client client.Client - job lmevalservicev1beta1.LMEvalJob - Option *DriverOption + client v1beta1.LMEvalJobUpdateServiceClient + grpcConn *grpc.ClientConn + Option *DriverOption } func NewDriver(opt *DriverOption) (Driver, error) { @@ -77,50 +91,90 @@ func NewDriver(opt *DriverOption) (Driver, error) { return nil, fmt.Errorf("JobNamespace or JobName is empty") } - return &driverImpl{Option: opt}, nil + conn, err := getGRPCClientConn(opt) + if err != nil { + return nil, err + } + + return &driverImpl{ + client: v1beta1.NewLMEvalJobUpdateServiceClient(conn), + grpcConn: conn, + Option: opt, + }, nil } // Run implements Driver. func (d *driverImpl) Run() error { - config, err := ctrl.GetConfig() - if err != nil { + if err := d.updateStatus(lmevalservicev1beta1.RunningJobState); err != nil { return err } + err := d.exec() - client, err := client.New(config, client.Options{Scheme: scheme}) - if err != nil { - return err - } + return d.updateCompleteStatus(err) +} - d.client = client - if err := d.getJob(); err != nil { - return err +func (d *driverImpl) Cleanup() { + if d != nil && d.grpcConn != nil { + d.grpcConn.Close() } +} - if err := d.updateStatus(lmevalservicev1beta1.RunningJobState); err != nil { - return err +func getGRPCClientConn(option *DriverOption) (clientConn *grpc.ClientConn, err error) { + // Set up a connection to the server. + if option.GrpcPort == 0 || option.GrpcService == "" { + return nil, fmt.Errorf("GrpcService or GrpcPort is not valid") } - err = d.exec() + serverAddr := fmt.Sprintf("%s:%d", option.GrpcService, option.GrpcPort) - if err := d.getJob(); err != nil { - return err - } + if viper.IsSet(GrpcServerCaEnv) { + serverCAPath := viper.GetString(GrpcServerCaEnv) - return d.updateCompleteStatus(err) -} + if viper.IsSet(GrpcClientCertEnv) && viper.IsSet(GrpcClientKeyEnv) { + // mTLS + certPath, keyPath := viper.GetString(GrpcClientCertEnv), viper.GetString(GrpcClientKeyEnv) + var cert tls.Certificate + cert, err = tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, err + } -func (d *driverImpl) getJob() error { - if err := d.client.Get(d.Option.Context, - types.NamespacedName{ - Name: d.Option.JobName, - Namespace: d.Option.JobNamespace, - }, - &d.job); err != nil { + ca := x509.NewCertPool() + var caBytes []byte + caBytes, err = os.ReadFile(serverCAPath) + if err != nil { + return nil, fmt.Errorf("failed to read server CA %q: %v", serverCAPath, err) + } + if ok := ca.AppendCertsFromPEM(caBytes); !ok { + return nil, fmt.Errorf("failed to parse server CA %q", serverCAPath) + } - return err + tlsConfig := &tls.Config{ + ServerName: serverAddr, + Certificates: []tls.Certificate{cert}, + RootCAs: ca, + } + + clientConn, err = grpc.NewClient(serverAddr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + // TLS + creds, err := credentials.NewClientTLSFromFile(serverCAPath, serverAddr) + if err != nil { + return nil, fmt.Errorf("failed to load server CA: %v", err) + } + + clientConn, err = grpc.NewClient(serverAddr, grpc.WithTransportCredentials(creds)) + if err != nil { + return nil, fmt.Errorf("failed to connect to GRPC server: %v", err) + } + } + } else { + clientConn, err = grpc.NewClient( + serverAddr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) } - return nil + return } func (d *driverImpl) exec() error { @@ -142,7 +196,16 @@ func (d *driverImpl) exec() error { } berr := bufio.NewWriter(stderr) + executor := exec.Command(d.Option.Args[0], args...) + stdin, err := executor.StdinPipe() + if err != nil { + return err + } + executor.Stdout = bout + executor.Stderr = berr + defer func() { + stdin.Close() bout.Flush() stdout.Sync() stdout.Close() @@ -151,63 +214,89 @@ func (d *driverImpl) exec() error { stderr.Close() }() - fmt.Printf("args:%v\n", args) - executor := exec.Command(d.Option.Args[0], args...) - executor.Stdin = os.Stdin - executor.Stdout = bout - executor.Stderr = berr - - if err := executor.Run(); err != nil { - return err - } - return nil + // temporally fix the trust_remote_code issue + io.WriteString(stdin, "y\n") + return executor.Run() } func (d *driverImpl) updateStatus(state lmevalservicev1beta1.JobState) error { - if d.job.Status.State != state { - d.job.Status.State = state - if err := d.client.Status().Update(d.Option.Context, &d.job); err != nil { - d.Option.Logger.Error(err, "unable to update EvalJob.Status.State") - return err - } + ctx, cancel := context.WithTimeout(d.Option.Context, time.Second*10) + defer cancel() + + r, err := d.client.UpdateStatus(ctx, &v1beta1.JobStatus{ + JobName: d.Option.JobName, + JobNamespace: d.Option.JobNamespace, + State: string(state), + Reason: string(lmevalservicev1beta1.NoReason), + StatusMessage: "update status from the driver: running", + }) + + if r != nil && err == nil { + d.Option.Logger.Info(fmt.Sprintf("UpdateStatus done: %s", r.Message)) } - return nil + + return err } func (d *driverImpl) updateCompleteStatus(err error) error { - d.job.Status.State = lmevalservicev1beta1.CompleteJobState - d.job.Status.Reason = lmevalservicev1beta1.SucceedReason + ctx, cancel := context.WithTimeout(d.Option.Context, time.Second*10) + defer cancel() + newStatus := v1beta1.JobStatus{ + JobName: d.Option.JobName, + JobNamespace: d.Option.JobNamespace, + State: string(lmevalservicev1beta1.CompleteJobState), + Reason: string(lmevalservicev1beta1.SucceedReason), + StatusMessage: "update status from the driver: completed", + } + + var setErr = func(err error) { + newStatus.Reason = string(lmevalservicev1beta1.FailedReason) + newStatus.StatusMessage = err.Error() + } + if err != nil { - d.job.Status.Reason = lmevalservicev1beta1.FailedReason - d.job.Status.Message = err.Error() + setErr(err) } else { - // read the content of result*.json - pattern := filepath.Join(d.Option.OutputPath, "result*.json") - if err := filepath.WalkDir(d.Option.OutputPath, func(path string, dir fs.DirEntry, err error) error { - if err != nil { - return err - } - // only output directory - if path != d.Option.OutputPath && dir != nil && dir.IsDir() { - return fs.SkipDir - } + results, err := d.getResults() + if err != nil { + setErr(err) + } else { + newStatus.Results = &results + } + } - if matched, _ := filepath.Match(pattern, path); matched { - bytes, err := os.ReadFile(path) - if err != nil { - d.Option.Logger.Error(err, "failed to retrieve the results") - } else { - d.job.Status.Results = string(bytes) - } - } - return nil - }); err != nil { + r, err := d.client.UpdateStatus(ctx, &newStatus) + if r != nil && err == nil { + d.Option.Logger.Info(fmt.Sprintf("UpdateStatus with the results: %s", r.Message)) + } + + return err +} + +func (d *driverImpl) getResults() (string, error) { + var results string + pattern := filepath.Join(d.Option.OutputPath, "result*.json") + if err := filepath.WalkDir(d.Option.OutputPath, func(path string, dir fs.DirEntry, err error) error { + if err != nil { return err } + // only output directory + if path != d.Option.OutputPath && dir != nil && dir.IsDir() { + return fs.SkipDir + } + + if matched, _ := filepath.Match(pattern, path); matched { + bytes, err := os.ReadFile(path) + if err != nil { + d.Option.Logger.Error(err, "failed to retrieve the results") + } else { + results = string(bytes) + } + } + return nil + }); err != nil { + return "", err } - if err := d.client.Status().Update(d.Option.Context, &d.job); err != nil { - d.Option.Logger.Error(err, "unable to update EvalJob.Status.State to Complete") - return err - } - return nil + + return results, nil } diff --git a/backend/driver/driver_test.go b/backend/driver/driver_test.go index bb5cadb..6203304 100644 --- a/backend/driver/driver_test.go +++ b/backend/driver/driver_test.go @@ -44,7 +44,8 @@ func Test_Driver(t *testing.T) { Context: context.Background(), JobNamespace: "fms-lm-eval-service-system", JobName: "evaljob-sample", - ConfigMap: "configmap", + GrpcService: "lm-eval-grpc", + GrpcPort: 8082, OutputPath: ".", Logger: driverLog, Args: []string{"--", "sh", "-ec", "echo \"tttttttttttttttttttt\""}, diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 42b4ce2..a0b3d96 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -36,6 +36,7 @@ import ( lmevalservicev1beta1 "github.com/foundation-model-stack/fms-lm-eval-service/api/v1beta1" "github.com/foundation-model-stack/fms-lm-eval-service/backend/controller" + "github.com/spf13/viper" // +kubebuilder:scaffold:imports ) @@ -76,6 +77,7 @@ func main() { } opts.BindFlags(flag.CommandLine) flag.Parse() + viper.AutomaticEnv() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) @@ -127,13 +129,15 @@ func main() { os.Exit(1) } - if err = (&controller.LMEvalJobReconciler{ + ctor := &controller.LMEvalJobReconciler{ ConfigMap: configMap, Namespace: namespace, Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("lm-eval-service-controller"), - }).SetupWithManager(mgr); err != nil { + } + + if err = ctor.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "EvalJob") os.Exit(1) } diff --git a/cmd/driver/main.go b/cmd/driver/main.go index b46efa7..d90c87b 100644 --- a/cmd/driver/main.go +++ b/cmd/driver/main.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/foundation-model-stack/fms-lm-eval-service/backend/driver" + "github.com/spf13/viper" ) const ( @@ -38,7 +39,8 @@ var ( copy = flag.String("copy", "", "copy this binary to specified destination path") jobNameSpace = flag.String("job-namespace", "", "Job's namespace ") jobName = flag.String("job-name", "", "Job's name") - configMap = flag.String("configmap", "", "ConfigMap name") + grpcService = flag.String("grpc-service", "", "grpc service name") + grpcPort = flag.Int("grpc-port", 8082, "grpc port") outputPath = flag.String("output-path", OutputPath, "output path") driverLog = ctrl.Log.WithName("driver") ) @@ -50,6 +52,8 @@ func main() { opts.BindFlags(flag.CommandLine) flag.Parse() + viper.AutomaticEnv() + log.SetLogger(zap.New(zap.UseFlagOptions(&opts))) ctx := context.Background() args := flag.Args() @@ -75,7 +79,8 @@ func main() { JobNamespace: *jobNameSpace, JobName: *jobName, OutputPath: *outputPath, - ConfigMap: *configMap, + GrpcService: *grpcService, + GrpcPort: *grpcPort, Logger: driverLog, Args: args, } @@ -86,10 +91,13 @@ func main() { os.Exit(1) } + var exitCode = 0 if err := driver.Run(); err != nil { driverLog.Error(err, "Driver.Run failed") - os.Exit(1) + exitCode = 1 } + driver.Cleanup() + os.Exit(exitCode) } func CopyExec(destination string) (err error) { diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml index a11a3db..91a244e 100644 --- a/config/certmanager/certificate.yaml +++ b/config/certmanager/certificate.yaml @@ -33,3 +33,45 @@ spec: kind: Issuer name: selfsigned-issuer secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: certificate + app.kubernetes.io/instance: grpc-server-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: fms-lm-eval-service + app.kubernetes.io/part-of: fms-lm-eval-service + app.kubernetes.io/managed-by: kustomize + name: grpc-server-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc:PORT + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local:PORT + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: grpc-server-cert +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: certificate + app.kubernetes.io/instance: grpc-client-cert + app.kubernetes.io/component: certificate + app.kubernetes.io/created-by: fms-lm-eval-service + app.kubernetes.io/part-of: fms-lm-eval-service + app.kubernetes.io/managed-by: kustomize + name: grpc-client-cert + namespace: system +spec: + dnsNames: + - grpc-client + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: grpc-client-cert \ No newline at end of file diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 3dc9199..dd989f6 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -121,6 +121,7 @@ replacements: kind: Certificate group: cert-manager.io version: v1 + name: serving-cert fieldPaths: - .spec.dnsNames.0 - .spec.dnsNames.1 @@ -128,6 +129,30 @@ replacements: delimiter: '.' index: 0 create: true + - source: # Add cert-manager annotation to the GRPC Service + kind: Service + version: v1 + name: lm-eval-grpc + fieldPath: .metadata.name # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: grpc-server-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 0 + create: true + - select: + kind: ConfigMap + version: v1 + name: configmap + fieldPaths: + - .data.grpc-service - source: kind: Service version: v1 @@ -138,6 +163,25 @@ replacements: kind: Certificate group: cert-manager.io version: v1 + name: serving-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: '.' + index: 1 + create: true + - source: + kind: Service + version: v1 + name: lm-eval-grpc + fieldPath: .metadata.namespace # namespace of the service + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: grpc-server-cert fieldPaths: - .spec.dnsNames.0 - .spec.dnsNames.1 @@ -145,6 +189,24 @@ replacements: delimiter: '.' index: 1 create: true + - source: + kind: Service + version: v1 + name: lm-eval-grpc + fieldPath: .spec.ports.0.port # port number + targets: + - select: + kind: Certificate + group: cert-manager.io + version: v1 + name: grpc-server-cert + fieldPaths: + - .spec.dnsNames.0 + - .spec.dnsNames.1 + options: + delimiter: ':' + index: 1 + create: true - source: kind: ServiceAccount version: v1 diff --git a/config/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml index 738de35..a08d805 100644 --- a/config/default/manager_webhook_patch.yaml +++ b/config/default/manager_webhook_patch.yaml @@ -8,6 +8,13 @@ spec: spec: containers: - name: manager + env: + - name: GRPC_SERVER_CERT + value: /tmp/k8s-grpc-server/certs/tls.crt + - name: GRPC_SERVER_KEY + value: /tmp/k8s-grpc-server/certs/tls.key + - name: GRPC_CLIENT_CA + value: /tmp/k8s-grpc-client/certs/ca.crt ports: - containerPort: 9443 name: webhook-server @@ -16,8 +23,22 @@ spec: - mountPath: /tmp/k8s-webhook-server/serving-certs name: cert readOnly: true + - mountPath: /tmp/k8s-grpc-server/certs + name: grpc-server + readOnly: true + - mountPath: /tmp/k8s-grpc-client/certs + name: grpc-client + readOnly: true volumes: - name: cert secret: defaultMode: 420 secretName: webhook-server-cert + - name: grpc-server + secret: + defaultMode: 420 + secretName: grpc-server-cert + - name: grpc-client + secret: + defaultMode: 420 + secretName: grpc-client-cert diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index bd4cedb..f751eb0 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -5,6 +5,8 @@ data: driver-serviceaccount: driver pod-checking-interval: "10s" image-pull-policy: Always + grpc-service: THIS_WILL_BE_REPLACED + grpc-port: "8082" kind: ConfigMap metadata: name: configmap diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2a1c1d4..48d23c5 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,6 +1,7 @@ resources: - configmap.yaml - manager.yaml +- service.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 81bcf07..2ab24da 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -69,7 +69,7 @@ spec: resources: limits: cpu: 500m - memory: 128Mi + memory: 512Mi requests: cpu: 10m memory: 64Mi diff --git a/config/manager/service.yaml b/config/manager/service.yaml new file mode 100644 index 0000000..fec6730 --- /dev/null +++ b/config/manager/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: fms-lm-eval-service + app.kubernetes.io/managed-by: kustomize + name: grpc +spec: + ports: + - port: 8082 + protocol: TCP + targetPort: 8082 + selector: + control-plane: controller-manager diff --git a/config/rbac/driver_role.yaml b/config/rbac/driver_role.yaml deleted file mode 100644 index 6ea0f9b..0000000 --- a/config/rbac/driver_role.yaml +++ /dev/null @@ -1,38 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: driver-role -rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - watch -- apiGroups: - - "" - resources: - - pods - verbs: - - get - - list - - watch -- apiGroups: - - foundation-model-stack.github.com.github.com - resources: - - lmevaljobs - verbs: - - get - - list - - patch - - update - - watch -- apiGroups: - - foundation-model-stack.github.com.github.com - resources: - - lmevaljobs/status - verbs: - - get - - patch - - update diff --git a/config/rbac/driver_role_binding.yaml b/config/rbac/driver_role_binding.yaml deleted file mode 100644 index e54cd14..0000000 --- a/config/rbac/driver_role_binding.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/name: fms-lm-eval-service - app.kubernetes.io/managed-by: kustomize - name: driver-rolebinding -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: driver-role -subjects: -- kind: ServiceAccount - name: driver diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 73df365..fefb868 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -7,9 +7,7 @@ resources: - service_account.yaml - driver_service_account.yaml - role.yaml -- driver_role.yaml - role_binding.yaml -- driver_role_binding.yaml - leader_election_role.yaml - leader_election_role_binding.yaml # For each CRD, "Editor" and "Viewer" roles are scaffolded by diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index e083a48..2c0182c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -22,6 +22,14 @@ rules: - get - list - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch - apiGroups: - foundation-model-stack.github.com.github.com resources: diff --git a/docker/Dockerfile b/docker/Dockerfile index b2371f4..0ddbb0c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -16,7 +16,7 @@ RUN pip install --no-cache-dir --user --upgrade ibm-generative-ai[lm-eval] RUN pip install --no-cache-dir --user -r server/requirements.txt # Clone the Git repository and install the Python package RUN git clone https://github.com/EleutherAI/lm-evaluation-harness.git && \ - cd lm-evaluation-harness && \ + cd lm-evaluation-harness && git checkout 568af943e315100af3f00937bfd6947844769ab8 && \ curl --output lm_eval/models/bam.py https://raw.githubusercontent.com/IBM/ibm-generative-ai/main/src/genai/extensions/lm_eval/model.py && \ git apply /opt/app-root/src/server/patch/models.patch && pip install --no-cache-dir --user -e .[unitxt] diff --git a/go.mod b/go.mod index ed5dc07..e0498cf 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,12 @@ go 1.22.0 toolchain go1.22.3 require ( + github.com/go-logr/logr v1.4.1 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 + github.com/spf13/viper v1.19.0 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.2 k8s.io/api v0.30.0 k8s.io/apimachinery v0.30.0 k8s.io/client-go v0.30.0 @@ -16,11 +20,10 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect @@ -33,35 +36,46 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.16.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 // indirect - golang.org/x/net v0.25.0 // indirect - golang.org/x/oauth2 v0.12.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/term v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.21.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/oauth2 v0.18.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/term v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.30.0 // indirect diff --git a/go.sum b/go.sum index 0be2f95..17f4eb5 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,17 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= @@ -34,11 +37,13 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -47,8 +52,10 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -65,10 +72,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -80,10 +91,13 @@ github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8 github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= @@ -94,20 +108,37 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -117,59 +148,79 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20240529005216-23cca8864a10 h1:vpzMC/iZhYFAjJzHU0Cfuq+w1vLLsF2vLkDrPjzKYck= golang.org/x/exp v0.0.0-20240529005216-23cca8864a10/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= -golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= -golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=