diff --git a/clients/ui/bff/internal/api/app.go b/clients/ui/bff/internal/api/app.go index 7f9e377b97..555d57fe53 100644 --- a/clients/ui/bff/internal/api/app.go +++ b/clients/ui/bff/internal/api/app.go @@ -178,14 +178,6 @@ func NewApp(cfg config.EnvConfig, logger *slog.Logger) (*App, error) { return nil, fmt.Errorf("failed to create ModelRegistry Catalog client: %w", err) } - var modelCatalogSettingsRepository repositories.ModelCatalogSettingsRepositoryInterface - - if cfg.MockK8Client { - modelCatalogSettingsRepository, err = mocks.NewModelCatalogSettingsRepository(logger) - } else { - modelCatalogSettingsRepository, err = repositories.NewModelCatalogSettingsRepository(logger) - } - if err != nil { return nil, fmt.Errorf("failed to create ModelCatalogSettings client: %w", err) } @@ -194,7 +186,7 @@ func NewApp(cfg config.EnvConfig, logger *slog.Logger) (*App, error) { config: cfg, logger: logger, kubernetesClientFactory: k8sFactory, - repositories: repositories.NewRepositories(mrClient, modelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mrClient, modelCatalogClient), testEnv: testEnv, rootCAs: rootCAs, } diff --git a/clients/ui/bff/internal/api/app_test.go b/clients/ui/bff/internal/api/app_test.go index bccaf9374b..d592d31842 100644 --- a/clients/ui/bff/internal/api/app_test.go +++ b/clients/ui/bff/internal/api/app_test.go @@ -25,7 +25,7 @@ var _ = Describe("Static File serving Test", func() { } app := &App{ kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, config: envConfig, } diff --git a/clients/ui/bff/internal/api/healthcheck__handler_test.go b/clients/ui/bff/internal/api/healthcheck__handler_test.go index 54569dd2b4..de2ecc30c0 100644 --- a/clients/ui/bff/internal/api/healthcheck__handler_test.go +++ b/clients/ui/bff/internal/api/healthcheck__handler_test.go @@ -20,7 +20,7 @@ func TestHealthCheckHandler(t *testing.T) { app := App{config: config.EnvConfig{ Port: 4000, }, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), } rr := httptest.NewRecorder() diff --git a/clients/ui/bff/internal/api/model_catalog_settings_handler.go b/clients/ui/bff/internal/api/model_catalog_settings_handler.go index f831510a59..f4ac218bf9 100644 --- a/clients/ui/bff/internal/api/model_catalog_settings_handler.go +++ b/clients/ui/bff/internal/api/model_catalog_settings_handler.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/kubeflow/model-registry/ui/bff/internal/mocks" "net/http" "github.com/julienschmidt/httprouter" @@ -56,26 +57,15 @@ func (app *App) GetCatalogSourceConfigHandler(w http.ResponseWriter, r *http.Req return } - client, err := app.kubernetesClientFactory.GetClient(ctx) - if err != nil { - app.serverErrorResponse(w, r, errors.New("catalog client not found")) - return - } - catalogSourceId := ps.ByName(CatalogSourceId) - - catalogSourceConfig, err := app.repositories.ModelCatalogSettingsRepository.GetCatalogSourceConfig(ctx, client, namespace, catalogSourceId) - - if err != nil { - app.serverErrorResponse(w, r, err) - return - } + // TODO ppadti write the real implementation here + catalogSourceConfig := mocks.CreateSampleCatalogSource(catalogSourceId, "catalog-source-1", "yaml") modelCatalogSource := ModelCatalogSettingsSourceConfigEnvelope{ - Data: catalogSourceConfig, + Data: &catalogSourceConfig, } - err = app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) + err := app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) if err != nil { app.serverErrorResponse(w, r, err) @@ -92,27 +82,20 @@ func (app *App) CreateCatalogSourceConfigHandler(w http.ResponseWriter, r *http. return } - client, err := app.kubernetesClientFactory.GetClient(ctx) - if err != nil { - app.serverErrorResponse(w, r, errors.New("catalog client not found")) - return - } - var envelope ModelCatalogSourcePayloadEnvelope if err := json.NewDecoder(r.Body).Decode(&envelope); err != nil { app.serverErrorResponse(w, r, fmt.Errorf("error decoding JSON:: %v", err.Error())) return } - newCatalogSource, err := app.repositories.ModelCatalogSettingsRepository.CreateCatalogSourceConfig(ctx, client, namespace, *envelope.Data) - - if err != nil { - app.serverErrorResponse(w, r, err) - return - } + var sourceName = envelope.Data.Name + var sourceId = envelope.Data.Id + var sourceType = envelope.Data.Type + // TODO ppadti write the real implementation here + newCatalogSource := mocks.CreateSampleCatalogSource(sourceId, sourceName, sourceType) modelCatalogSource := ModelCatalogSettingsSourceConfigEnvelope{ - Data: newCatalogSource, + Data: &newCatalogSource, } w.Header().Set("Location", r.URL.JoinPath(modelCatalogSource.Data.Id).String()) @@ -133,30 +116,21 @@ func (app *App) UpdateCatalogSourceConfigHandler(w http.ResponseWriter, r *http. return } - client, err := app.kubernetesClientFactory.GetClient(ctx) - if err != nil { - app.serverErrorResponse(w, r, errors.New("catalog client not found")) - return - } - var envelope ModelCatalogSourcePayloadEnvelope if err := json.NewDecoder(r.Body).Decode(&envelope); err != nil { app.serverErrorResponse(w, r, fmt.Errorf("error decoding JSON:: %v", err.Error())) return } - newCatalogSource, err := app.repositories.ModelCatalogSettingsRepository.UpdateCatalogSourceConfig(ctx, client, namespace, *envelope.Data) - - if err != nil { - app.serverErrorResponse(w, r, err) - return - } + catalogSourceId := envelope.Data.Id + // TODO ppadti write the real implementation here + newCatalogSource := mocks.CreateSampleCatalogSource(catalogSourceId, "Updated Catalog", "yaml") modelCatalogSource := ModelCatalogSettingsSourceConfigEnvelope{ - Data: newCatalogSource, + Data: &newCatalogSource, } - err = app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) + err := app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) if err != nil { app.serverErrorResponse(w, r, err) @@ -172,27 +146,16 @@ func (app *App) DeleteCatalogSourceConfigHandler(w http.ResponseWriter, r *http. return } - client, err := app.kubernetesClientFactory.GetClient(ctx) - if err != nil { - app.serverErrorResponse(w, r, errors.New("catalog client not found")) - return - } - - // this is the temoprary fix to start fronetend development + // TODO ppadti write the real implementation here catalogSourceId := ps.ByName(CatalogSourceId) - newCatalogSource, err := app.repositories.ModelCatalogSettingsRepository.DeleteCatalogSourceConfig(ctx, client, namespace, catalogSourceId) - - if err != nil { - app.serverErrorResponse(w, r, err) - return - } + deletedCatalogSource := mocks.CreateSampleCatalogSource(catalogSourceId, "Updated Catalog", "yaml") modelCatalogSource := ModelCatalogSettingsSourceConfigEnvelope{ - Data: newCatalogSource, + Data: &deletedCatalogSource, } - err = app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) + err := app.WriteJSON(w, http.StatusOK, modelCatalogSource, nil) if err != nil { app.serverErrorResponse(w, r, err) diff --git a/clients/ui/bff/internal/api/model_registry_handler_test.go b/clients/ui/bff/internal/api/model_registry_handler_test.go index fe7d73a8e2..e9195c4ec8 100644 --- a/clients/ui/bff/internal/api/model_registry_handler_test.go +++ b/clients/ui/bff/internal/api/model_registry_handler_test.go @@ -23,7 +23,7 @@ var _ = Describe("TestModelRegistryHandler", func() { By("creating the test app") testApp := App{ kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } diff --git a/clients/ui/bff/internal/api/model_registry_settings_groups_handler_test.go b/clients/ui/bff/internal/api/model_registry_settings_groups_handler_test.go index 7a9eacdba6..f1dcd01995 100644 --- a/clients/ui/bff/internal/api/model_registry_settings_groups_handler_test.go +++ b/clients/ui/bff/internal/api/model_registry_settings_groups_handler_test.go @@ -23,7 +23,7 @@ var _ = Describe("TestGroupsHandler", func() { testApp = App{ config: config.EnvConfig{DeploymentMode: config.DeploymentModeStandalone}, kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } }) diff --git a/clients/ui/bff/internal/api/model_registry_settings_rbac_handler_test.go b/clients/ui/bff/internal/api/model_registry_settings_rbac_handler_test.go index e2c50ebd5f..7421be1146 100644 --- a/clients/ui/bff/internal/api/model_registry_settings_rbac_handler_test.go +++ b/clients/ui/bff/internal/api/model_registry_settings_rbac_handler_test.go @@ -28,7 +28,7 @@ var _ = Describe("TestRoleBindingHandlers", func() { testApp = App{ config: config.EnvConfig{DeploymentMode: config.DeploymentModeStandalone}, kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } }) diff --git a/clients/ui/bff/internal/api/namespaces_handler_test.go b/clients/ui/bff/internal/api/namespaces_handler_test.go index 3d18c78463..eaacffe0cd 100644 --- a/clients/ui/bff/internal/api/namespaces_handler_test.go +++ b/clients/ui/bff/internal/api/namespaces_handler_test.go @@ -27,7 +27,7 @@ var _ = Describe("TestNamespacesHandler", func() { testApp = App{ config: config.EnvConfig{DevMode: true}, kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } }) @@ -137,7 +137,7 @@ var _ = Describe("TestNamespacesHandler", func() { testApp = App{ config: config.EnvConfig{DevMode: true}, kubernetesClientFactory: kubernetesMockedTokenClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } }) diff --git a/clients/ui/bff/internal/api/suite_test.go b/clients/ui/bff/internal/api/suite_test.go index c388df0786..8eb06bab78 100644 --- a/clients/ui/bff/internal/api/suite_test.go +++ b/clients/ui/bff/internal/api/suite_test.go @@ -28,7 +28,6 @@ var ( kubernetesMockedStaticClientFactory k8s.KubernetesClientFactory mockMRClient *mocks.ModelRegistryClientMock mockModelCatalogClient *mocks.ModelCatalogClientMock - modelCatalogSettingsRepository *mocks.ModelCatalogSettingsRepositoryMock ctx context.Context cancel context.CancelFunc logger *slog.Logger @@ -70,8 +69,6 @@ var _ = BeforeSuite(func() { mockModelCatalogClient, err = mocks.NewModelCatalogClientMock(nil) Expect(err).NotTo(HaveOccurred()) - modelCatalogSettingsRepository, err = mocks.NewModelCatalogSettingsRepository(nil) - Expect(err).NotTo(HaveOccurred()) }) var _ = AfterSuite(func() { diff --git a/clients/ui/bff/internal/api/test_utils.go b/clients/ui/bff/internal/api/test_utils.go index 61617cf1b1..72a1b0c2d3 100644 --- a/clients/ui/bff/internal/api/test_utils.go +++ b/clients/ui/bff/internal/api/test_utils.go @@ -29,10 +29,6 @@ func setupApiTest[T any](method string, url string, body interface{}, k8Factory if err != nil { return *new(T), nil, err } - modelCatalogSettingsRepository, err := mocks.NewModelCatalogSettingsRepository(nil) - if err != nil { - return *new(T), nil, err - } mockClient := new(mocks.MockHTTPClient) @@ -44,7 +40,7 @@ func setupApiTest[T any](method string, url string, body interface{}, k8Factory cfg.AuthMethod = config.AuthMethodUser } testApp := App{ - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), kubernetesClientFactory: k8Factory, logger: slog.Default(), config: cfg, diff --git a/clients/ui/bff/internal/api/user_handler_test.go b/clients/ui/bff/internal/api/user_handler_test.go index 168f896a29..812e9de69c 100644 --- a/clients/ui/bff/internal/api/user_handler_test.go +++ b/clients/ui/bff/internal/api/user_handler_test.go @@ -28,7 +28,7 @@ var _ = Describe("TestUserHandler", func() { By("creating the test app") testApp = App{ kubernetesClientFactory: kubernetesMockedStaticClientFactory, - repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient, modelCatalogSettingsRepository), + repositories: repositories.NewRepositories(mockMRClient, mockModelCatalogClient), logger: logger, } }) diff --git a/clients/ui/bff/internal/integrations/kubernetes/client.go b/clients/ui/bff/internal/integrations/kubernetes/client.go index e0ba1f1a07..fcb80e4559 100644 --- a/clients/ui/bff/internal/integrations/kubernetes/client.go +++ b/clients/ui/bff/internal/integrations/kubernetes/client.go @@ -2,13 +2,17 @@ package kubernetes import ( "context" - corev1 "k8s.io/api/core/v1" ) const ComponentLabelValue = "model-registry" const ComponentLabelValueCatalog = "model-catalog" +// TODO ppadti double check if the config map key is indeed sources.yaml +const CatalogSourceKey = "sources.yaml" +const CatalogSourceDefaultConfigMapName = "model-catalog-source-config" +const CatalogSourceUserConfigMapName = "model-catalog-sources" + type KubernetesClientInterface interface { // Service discovery GetServiceNames(ctx context.Context, namespace string) ([]string, error) @@ -30,4 +34,8 @@ type KubernetesClientInterface interface { // Model Registry Settings GetGroups(ctx context.Context) ([]string, error) + + //Model Catalog Settings + GetAllCatalogSourceConfigs(ctx context.Context, namespace string) (corev1.ConfigMap, corev1.ConfigMap, error) + //TODO ppadti add other methods here } diff --git a/clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go b/clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go index fca262b814..d7af1e514b 100644 --- a/clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go +++ b/clients/ui/bff/internal/integrations/kubernetes/k8mocks/base_testenv.go @@ -7,8 +7,9 @@ import ( "os" "path/filepath" "runtime" + "strings" - kubernetes2 "github.com/kubeflow/model-registry/ui/bff/internal/integrations/kubernetes" + k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations/kubernetes" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -162,6 +163,131 @@ func setupMock(mockK8sClient kubernetes.Interface, ctx context.Context) error { return fmt.Errorf("failed to set up group access to namespace: %w", err) } + //TODO ppadti: Add more mock setup as needed for other namespaces + err = createModelCatalogDefaultSourcesConfigMap(mockK8sClient, ctx, "kubeflow") + if err != nil { + return err + } + + err = createModelCatalogSourcesConfigMap(mockK8sClient, ctx, "kubeflow") + if err != nil { + return err + } + + return nil +} + +func createModelCatalogDefaultSourcesConfigMap( + k8sClient kubernetes.Interface, + ctx context.Context, + namespace string, +) error { + raw := strings.TrimSpace(` +catalogs: + - name: Dora AI + id: dora_ai_models + type: yaml + enabled: true + properties: + yamlCatalogPath: /shared-data/models-catalog.yaml + labels: + - Dora AI + + - name: Bella AI validated + id: bella_ai_validated_models + type: yaml + enabled: true + properties: + yamlCatalogPath: /shared-data/validated-models-catalog.yaml + labels: + - Bella AI validated +`) + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8s.CatalogSourceDefaultConfigMapName, + Namespace: namespace, + }, + Data: map[string]string{ + k8s.CatalogSourceKey: raw, + }, + } + + if _, err := k8sClient.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("failed to create model-catalog-default-sources configmap: %w", err) + } + + return nil +} + +func createModelCatalogSourcesConfigMap( + k8sClient kubernetes.Interface, + ctx context.Context, + namespace string, +) error { + raw := strings.TrimSpace(` +catalogs: + - name: Custom yaml + id: custom_yaml_models + type: yaml + enabled: true + properties: + yamlCatalogPath: /shared-data/models-catalog.yaml + includedModels: + - model-* + - model-2-* + excludedModels: + - sample-model-* + labels: + - Dora AI + + - name: Sample source + id: sample_source_models + type: yaml + enabled: false + properties: + yamlCatalogPath: /shared-data/validated-models-catalog.yaml + includedModels: + - model-* + - model-2-* + excludedModels: + - sample-model-* + labels: + - Bella AI validated + - Dora AI + + - name: Hugging face source + id: hugging_face_source + type: huggingface + enabled: true + properties: + apiKey: accessToken + allowedOrganization: org + includedModels: + - model-* + - model-2-* + excludedModels: + - sample-model-* + labels: + - Bella AI validated +`) + + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: k8s.CatalogSourceUserConfigMapName, + Namespace: namespace, + }, + Data: map[string]string{ + k8s.CatalogSourceKey: raw, + "custom_yaml_models.yaml": "models:\n - name: model1", + "sample_source_models.yaml": "models:\n - name: model2", + }, + } + + if _, err := k8sClient.CoreV1().ConfigMaps(namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil { + return fmt.Errorf("failed to create model-catalog-default-sources configmap: %w", err) + } + return nil } @@ -373,7 +499,7 @@ func createService(k8sClient kubernetes.Interface, ctx context.Context, name str }, Spec: corev1.ServiceSpec{ Selector: map[string]string{ - "component": kubernetes2.ComponentLabelValue, + "component": k8s.ComponentLabelValue, }, Type: corev1.ServiceTypeClusterIP, ClusterIP: clusterIP, diff --git a/clients/ui/bff/internal/integrations/kubernetes/shared_k8s_client.go b/clients/ui/bff/internal/integrations/kubernetes/shared_k8s_client.go index 1734e4e354..e125f1e674 100644 --- a/clients/ui/bff/internal/integrations/kubernetes/shared_k8s_client.go +++ b/clients/ui/bff/internal/integrations/kubernetes/shared_k8s_client.go @@ -158,3 +158,42 @@ func (kc *SharedClientLogic) GetGroups(ctx context.Context) ([]string, error) { kc.Logger.Info("This functionality is not implement yet. This is a STUB API to unblock frontend development until we have a definition on how to create model registries") return []string{}, nil } + +func (kc *SharedClientLogic) GetAllCatalogSourceConfigs( + sessionCtx context.Context, + namespace string, +) (corev1.ConfigMap, corev1.ConfigMap, error) { + + if namespace == "" { + return corev1.ConfigMap{}, corev1.ConfigMap{}, fmt.Errorf("namespace cannot be empty") + } + + sessionLogger := sessionCtx.Value(constants.TraceLoggerKey).(*slog.Logger) + + // Fetch default sources + defaultCM, err := kc.Client.CoreV1(). + ConfigMaps(namespace). + Get(sessionCtx, CatalogSourceDefaultConfigMapName, metav1.GetOptions{}) + + if err != nil { + sessionLogger.Error("failed to fetch default catalog source configmap", + "namespace", namespace, + "name", CatalogSourceDefaultConfigMapName, + "error", err, + ) + return corev1.ConfigMap{}, corev1.ConfigMap{}, fmt.Errorf("failed to get %s: %w", CatalogSourceDefaultConfigMapName, err) + } + + userCM, err := kc.Client.CoreV1().ConfigMaps(namespace).Get(sessionCtx, CatalogSourceUserConfigMapName, metav1.GetOptions{}) + + if err != nil { + sessionLogger.Error("failed to fetch catalog source configmap", + "namespace", namespace, + "name", CatalogSourceUserConfigMapName, + "error", err, + ) + return corev1.ConfigMap{}, corev1.ConfigMap{}, fmt.Errorf("failed to get %s: %w", CatalogSourceUserConfigMapName, err) + } + + return *defaultCM, *userCM, nil +} diff --git a/clients/ui/bff/internal/mocks/model_catalog_settings_mock.go b/clients/ui/bff/internal/mocks/model_catalog_settings_mock.go deleted file mode 100644 index 850ee13dac..0000000000 --- a/clients/ui/bff/internal/mocks/model_catalog_settings_mock.go +++ /dev/null @@ -1,63 +0,0 @@ -package mocks - -import ( - "context" - "fmt" - "log/slog" - - k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations/kubernetes" - "github.com/kubeflow/model-registry/ui/bff/internal/models" -) - -type ModelCatalogSettingsRepositoryMock struct { -} - -func NewModelCatalogSettingsRepository(logger *slog.Logger) (*ModelCatalogSettingsRepositoryMock, error) { - return &ModelCatalogSettingsRepositoryMock{}, nil -} - -func (m *ModelCatalogSettingsRepositoryMock) GetAllCatalogSourceConfigs(_ context.Context, _ k8s.KubernetesClientInterface, _ string) (*models.CatalogSourceConfigList, error) { - allCatalogSourceConfigs := GetCatalogSourceConfigListMock() - - return &allCatalogSourceConfigs, nil -} - -func (m *ModelCatalogSettingsRepositoryMock) GetCatalogSourceConfig(_ context.Context, _ k8s.KubernetesClientInterface, _ string, catalogSourceId string) (*models.CatalogSourceConfig, error) { - catalogSourceConfig := CreateSampleCatalogSource(catalogSourceId, "catalog-source-1", "yaml") - - return &catalogSourceConfig, nil -} - -func (m *ModelCatalogSettingsRepositoryMock) CreateCatalogSourceConfig(_ context.Context, _ k8s.KubernetesClientInterface, _ string, sourceConfigPayload models.CatalogSourceConfigPayload) (*models.CatalogSourceConfig, error) { - var sourceName = sourceConfigPayload.Name - var sourceId = sourceConfigPayload.Id - var sourceType = sourceConfigPayload.Type - - if sourceName == "" { - return nil, fmt.Errorf("source name is required") - } - if sourceId == "" { - return nil, fmt.Errorf("source ID is required") - } - if sourceType == "" { - return nil, fmt.Errorf("source type is required") - } - - newCatalogSource := CreateSampleCatalogSource(sourceId, sourceName, sourceType) - - return &newCatalogSource, nil -} - -func (m *ModelCatalogSettingsRepositoryMock) UpdateCatalogSourceConfig(_ context.Context, _ k8s.KubernetesClientInterface, _ string, sourceConfigPayload models.CatalogSourceConfigPayload) (*models.CatalogSourceConfig, error) { - catalogSourceId := sourceConfigPayload.Id - - updatedCatalogSource := CreateSampleCatalogSource(catalogSourceId, "Updated Catalog", "yaml") - - return &updatedCatalogSource, nil -} - -func (m *ModelCatalogSettingsRepositoryMock) DeleteCatalogSourceConfig(_ context.Context, _ k8s.KubernetesClientInterface, _ string, catalogSourceId string) (*models.CatalogSourceConfig, error) { - deletedCatalogSource := CreateSampleCatalogSource(catalogSourceId, "Updated Catalog", "yaml") - - return &deletedCatalogSource, nil -} diff --git a/clients/ui/bff/internal/repositories/model_catalog_settings.go b/clients/ui/bff/internal/repositories/model_catalog_settings.go index bd3bd7a27d..bfce454f29 100644 --- a/clients/ui/bff/internal/repositories/model_catalog_settings.go +++ b/clients/ui/bff/internal/repositories/model_catalog_settings.go @@ -3,29 +3,46 @@ package repositories import ( "context" "fmt" - "log/slog" k8s "github.com/kubeflow/model-registry/ui/bff/internal/integrations/kubernetes" "github.com/kubeflow/model-registry/ui/bff/internal/models" + "gopkg.in/yaml.v3" ) -type ModelCatalogSettingsRepositoryInterface interface { - GetAllCatalogSourceConfigs(ctx context.Context, k8sClient k8s.KubernetesClientInterface, namespace string) (*models.CatalogSourceConfigList, error) - GetCatalogSourceConfig(ctx context.Context, k8sClient k8s.KubernetesClientInterface, namespace string, catalogSourceId string) (*models.CatalogSourceConfig, error) - CreateCatalogSourceConfig(ctx context.Context, k8sClient k8s.KubernetesClientInterface, namespace string, payload models.CatalogSourceConfigPayload) (*models.CatalogSourceConfig, error) - UpdateCatalogSourceConfig(ctx context.Context, k8sClient k8s.KubernetesClientInterface, namespace string, payload models.CatalogSourceConfigPayload) (*models.CatalogSourceConfig, error) - DeleteCatalogSourceConfig(ctx context.Context, k8sClient k8s.KubernetesClientInterface, namespace string, catalogSourceId string) (*models.CatalogSourceConfig, error) -} - type ModelCatalogSettingsRepository struct { } -func NewModelCatalogSettingsRepository(logger *slog.Logger) (*ModelCatalogSettingsRepository, error) { - return &ModelCatalogSettingsRepository{}, nil +func NewModelCatalogSettingsRepository() *ModelCatalogSettingsRepository { + return &ModelCatalogSettingsRepository{} } -func (r *ModelCatalogSettingsRepository) GetAllCatalogSourceConfigs(_ context.Context, _ k8s.KubernetesClientInterface, namespace string) (*models.CatalogSourceConfigList, error) { - return nil, fmt.Errorf("not implemented yet") +func (r *ModelCatalogSettingsRepository) GetAllCatalogSourceConfigs(ctx context.Context, client k8s.KubernetesClientInterface, namespace string) (*models.CatalogSourceConfigList, error) { + defaultCM, userCM, err := client.GetAllCatalogSourceConfigs(ctx, namespace) + if err != nil { + return nil, fmt.Errorf("failed to fetch catalog source configmaps: %w", err) + } + + catalogSources := &models.CatalogSourceConfigList{ + Catalogs: make([]models.CatalogSourceConfig, 0), + } + + if raw, ok := defaultCM.Data[k8s.CatalogSourceKey]; ok { + defaulfCatalogSources, err := parseCatalogYaml(raw, true) + if err != nil { + return nil, fmt.Errorf("failed to parse default catalogs: %w", err) + } + catalogSources.Catalogs = append(catalogSources.Catalogs, defaulfCatalogSources...) + } + + if raw, ok := userCM.Data[k8s.CatalogSourceKey]; ok { + userManagedSources, err := parseCatalogYaml(raw, false) + if err != nil { + return nil, fmt.Errorf("failed to parse default catalogs: %w", err) + } + catalogSources.Catalogs = append(catalogSources.Catalogs, userManagedSources...) + } + + return catalogSources, nil } func (r *ModelCatalogSettingsRepository) GetCatalogSourceConfig(ctx context.Context, @@ -33,6 +50,7 @@ func (r *ModelCatalogSettingsRepository) GetCatalogSourceConfig(ctx context.Cont namespace string, catalogSourceId string, ) (*models.CatalogSourceConfig, error) { + // TODO ppadti write the real implementation here calling k8s client return nil, fmt.Errorf("not implemented yet") } @@ -42,6 +60,8 @@ func (r *ModelCatalogSettingsRepository) CreateCatalogSourceConfig( namespace string, payload models.CatalogSourceConfigPayload, ) (*models.CatalogSourceConfig, error) { + + // TODO ppadti write the real implementation here calling k8s client return nil, fmt.Errorf("not implemented yet") } @@ -51,6 +71,7 @@ func (r *ModelCatalogSettingsRepository) UpdateCatalogSourceConfig( namespace string, payload models.CatalogSourceConfigPayload, ) (*models.CatalogSourceConfig, error) { + // TODO ppadti write the real implementation here calling k8s client return nil, fmt.Errorf("not implemented yet") } @@ -60,5 +81,74 @@ func (r *ModelCatalogSettingsRepository) DeleteCatalogSourceConfig( namespace string, catalogSourceId string, ) (*models.CatalogSourceConfig, error) { + // TODO ppadti write the real implementation here calling k8s client return nil, fmt.Errorf("not implemented yet") } + +func parseCatalogYaml(raw string, isDefault bool) ([]models.CatalogSourceConfig, error) { + // Internal struct to match YAML structure + var parsed struct { + Catalogs []struct { + Name string `yaml:"name"` + Id string `yaml:"id"` + Type string `yaml:"type"` + Enabled *bool `yaml:"enabled"` + Properties map[string]interface{} `yaml:"properties"` + Labels []string `yaml:"labels"` + } `yaml:"catalogs"` + } + + if err := yaml.Unmarshal([]byte(raw), &parsed); err != nil { + return nil, fmt.Errorf("failed to parse catalogs yaml: %w", err) + } + + catalogs := make([]models.CatalogSourceConfig, 0, len(parsed.Catalogs)) + for _, c := range parsed.Catalogs { + entry := models.CatalogSourceConfig{ + Id: c.Id, + Name: c.Name, + Type: c.Type, + Enabled: c.Enabled, + Labels: c.Labels, + IsDefault: &isDefault, + } + + if c.Properties != nil { + if includedModels, ok := c.Properties["includedModels"]; ok { + entry.IncludedModels = extractStringSlice(includedModels) + } + + if excludedModels, ok := c.Properties["excludedModels"]; ok { + entry.ExcludedModels = extractStringSlice(excludedModels) + } + + if apiKey, ok := c.Properties["apiKey"].(string); ok { + entry.ApiKey = &apiKey + } + + if allowedOrganization, ok := c.Properties["allowedOrganization"].(string); ok { + entry.AllowedOrganization = &allowedOrganization + } + } + catalogs = append(catalogs, entry) + } + + return catalogs, nil +} + +func extractStringSlice(value interface{}) []string { + if arr, ok := value.([]interface{}); ok { + result := make([]string, 0, len(arr)) + for _, item := range arr { + if str, ok := item.(string); ok { + result = append(result, str) + } + } + return result + } + if strSlice, ok := value.([]string); ok { + return strSlice + } + return []string{} + +} diff --git a/clients/ui/bff/internal/repositories/repositories.go b/clients/ui/bff/internal/repositories/repositories.go index 86dcaba9d3..e325f358f0 100644 --- a/clients/ui/bff/internal/repositories/repositories.go +++ b/clients/ui/bff/internal/repositories/repositories.go @@ -8,12 +8,12 @@ type Repositories struct { ModelRegistrySettings *ModelRegistrySettingsRepository ModelRegistryClient ModelRegistryClientInterface ModelCatalogClient ModelCatalogClientInterface - ModelCatalogSettingsRepository ModelCatalogSettingsRepositoryInterface + ModelCatalogSettingsRepository *ModelCatalogSettingsRepository User *UserRepository Namespace *NamespaceRepository } -func NewRepositories(modelRegistryClient ModelRegistryClientInterface, modelCatalogClient ModelCatalogClientInterface, modelCatalogSettingsRepository ModelCatalogSettingsRepositoryInterface) *Repositories { +func NewRepositories(modelRegistryClient ModelRegistryClientInterface, modelCatalogClient ModelCatalogClientInterface) *Repositories { return &Repositories{ HealthCheck: NewHealthCheckRepository(), ModelRegistry: NewModelRegistryRepository(), @@ -21,7 +21,7 @@ func NewRepositories(modelRegistryClient ModelRegistryClientInterface, modelCata ModelCatalogClient: modelCatalogClient, ModelRegistrySettings: NewModelRegistrySettingsRepository(), ModelRegistryClient: modelRegistryClient, - ModelCatalogSettingsRepository: modelCatalogSettingsRepository, + ModelCatalogSettingsRepository: NewModelCatalogSettingsRepository(), User: NewUserRepository(), Namespace: NewNamespaceRepository(), }