Skip to content

feat(ai-integrations): RHIDP-15201: kserve-kubeflow-connector-backend — Migrate K8s Credentials and Plugin Config to app-config.yaml #4181

Description

@gabemontero

The kserve-kubeflow-connector-backend plugin creates its K8s clients via KubeConfig.loadFromDefault(), which reads from ~/.kube/config (e.g., after oc login) or the KUBECONFIG env var, and optionally overrides the token with a K8S_TOKEN environment variable. This works for local development but does not follow Backstage conventions — platform engineers expect to configure K8s cluster access through app-config.yaml, not environment variables. The OCM plugin provides the reference pattern: cluster credentials either directly in the provider config or via a kubernetesPluginRef that references a cluster defined in the Backstage kubernetes plugin's kubernetes.clusterLocatorMethods config.

Additionally, two type-level issues have accumulated:

  1. ModelCatalogConfig.baseUrl in the entity provider is unused in practice — if the Backstage DiscoveryService cannot resolve the connector, nothing else works either (auth tokens, endpoint calls), so the fallback adds complexity without value.
  2. ConnectorConfig and ReconcilerConfig in the connector plugin are redundant — ConnectorConfig exists solely to be unpacked into ReconcilerConfig five lines later in setupInformer.

Tasks

From openspec/changes/k8s-config-migration/tasks.md:

Section 1 — Remove baseUrl from Entity Provider:

  • 1.1 In plugins/catalog-backend-module-model-catalog/src/providers/types.ts, remove baseUrl from ModelCatalogConfig
  • 1.2 In plugins/catalog-backend-module-model-catalog/src/providers/config.ts, remove baseUrl reading from readModelCatalogApiEntityConfig
  • 1.3 In plugins/catalog-backend-module-model-catalog/src/providers/ModelCatalogResourceEntityProvider.ts: remove baseUrl field and simplify run() to use const url = await this.discovery.getBaseUrl(this.name) directly
  • 1.4 In plugins/catalog-backend-module-model-catalog/config.d.ts, remove baseUrl?: string from the connector-level fields
  • 1.5 Verify yarn tsc passes

Section 2 — Merge ConnectorConfig into ReconcilerConfig:

  • 2.1 In plugins/kserve-kubeflow-connector-backend/src/services/types.ts, change defaultLifecycle and defaultOwner from required string to optional ?: string on ReconcilerConfig
  • 2.2 In plugins/kserve-kubeflow-connector-backend/src/services/InformerService.ts: remove ConnectorConfig interface and export; update setupInformer signature from (connectorConfig?: ConnectorConfig) to (config: ReconcilerConfig, logger: LoggerService); apply defaults for defaultOwner and defaultLifecycle inside setupInformer
  • 2.3 In plugins/kserve-kubeflow-connector-backend/src/plugin.ts: remove ConnectorConfig import, import ReconcilerConfig from ./services/types, build ReconcilerConfig directly, pass logger (from init deps) to setupInformer
  • 2.4 Update any re-exports of ConnectorConfig
  • 2.5 Verify yarn tsc passes

Section 3 — Add K8s Connection Fields to ReconcilerConfig:

  • 3.1 Add clusterName?, url?, serviceAccountToken?, skipTLSVerify?, caData? to ReconcilerConfig in types.ts
  • 3.2 Verify yarn tsc passes

Section 4 — Implement KubeConfig from Config Fields:

  • 4.1 In setupInformer, build KubeConfig via loadFromOptions() when config.url && config.serviceAccountToken are present; log partial config warning when only one is present; fall back to loadFromDefault() otherwise
  • 4.2 Simplify token extraction: use config.serviceAccountToken when config fields present, keep existing extraction logic + K8S_TOKEN override for loadFromDefault() path. loadFromDefault() also supports KUBECONFIG env var and ~/.kube/config from oc login
  • 4.3 Store resolved token on config.serviceAccountToken (NOT config.k8sToken). Remove k8sToken field entirely — serviceAccountToken is the single token field. Update all code reading config.k8sToken to read config.serviceAccountToken
  • 4.4 Add logging: log cluster URL (NOT the token) and config source
  • 4.5 Verify yarn tsc passes

Section 5 — Implement kubernetesPluginRef Lookup in plugin.ts:

Note: Use safeGetOptionalString (see AGENTS.md ConfigReader getOptionalString() edge case) for all optional string config reads. Duplicate the helper locally — do NOT import the url-reader variant which returns '' on error.

  • 5.1 Check for kubernetesPluginRef in cluster sub-config
  • 5.2 If set, look up the cluster in kubernetes.clusterLocatorMethods using a matched flag pattern; on match extract url, serviceAccountToken, skipTLSVerify, caData; post-match validate BOTH url AND serviceAccountToken — if either is missing, log warning with field status, clear all stale K8s fields, fall through to direct config
  • 5.3 When match succeeds, check authProvider field — if set and not serviceAccount, log warning but proceed
  • 5.4 If not set or lookup produced incomplete K8s fields, read K8s fields directly from cluster sub-config (gate on !reconcilerConfig.url || !reconcilerConfig.serviceAccountToken)
  • 5.5 Verify yarn tsc passes

Section 6 — Update config.d.ts Schema:

  • 6.1 Add K8s connection fields (url, serviceAccountToken, skipTLSVerify, caData, kubernetesPluginRef) with proper @visibility annotations to the [clusterKey: string] union in config.d.ts
  • 6.2 Verify baseUrl removal persisted from Task 1.4
  • 6.3 Verify yarn tsc passes

Section 7 — Update app-config.yaml:

  • 7.1 Add K8s connection fields to cluster-1 section
  • 7.2 Add commented-out kubernetesPluginRef alternative
  • 7.3 Add commented-out kubernetes section for use with kubernetesPluginRef

Section 8 — Add K8s RBAC Example YAML:

  • 8.1 Create examples/k8s-rbac.yaml with ServiceAccount, ClusterRole (InferenceServices: get/watch/list; Routes: get/list; ServiceAccounts: get/list), and ClusterRoleBinding
  • 8.2 Verify RBAC rules match actual K8s API calls in InformerService.ts and Catalog.ts

Section 9 — Replace console.log with logger in setupInformer:

  • 9.1 Replace console.log / console.error calls inside setupInformer with logger.info / logger.error / logger.debug. Only change functions that receive the logger — leave informer event handlers as-is
  • 9.2 Verify yarn tsc passes

Section 10 — Verification:

  • 10.1 yarn tsc passes with no errors
  • 10.2 yarn build:all succeeds
  • 10.3 Existing unit tests pass (yarn test:all)
  • 10.4 Prettier checks pass (yarn prettier)
  • 10.5 Lint checks pass (yarn lint:all)
  • 10.6 Plugin starts with direct K8s config in app-config.yaml and connects to cluster
  • 10.7 Plugin starts with kubernetesPluginRef and connects to cluster
  • 10.8 Plugin starts with no K8s config (falls back to loadFromDefault()) — verify with ~/.kube/config from oc login
  • 10.9 Plugin starts with KUBECONFIG env var pointing to a custom kubeconfig file
  • 10.10 K8S_TOKEN env var still works as token override when using loadFromDefault()
  • 10.11 Connector discovers and watches InferenceServices using config-based credentials

Specifications

  • openspec/changes/k8s-config-migration/proposal.md — scope, capabilities added/modified, non-goals
  • openspec/changes/k8s-config-migration/design.md — Decisions D1-D8 (OCM config pattern, baseUrl removal, ConnectorConfig merge, loadFromOptions, serviceAccount auth, config.d.ts, precedence chain, K8s RBAC)
  • openspec/changes/k8s-config-migration/specs/k8s-config/spec.md — WHEN/THEN behavioral requirements for all capabilities
  • Parent openspec: openspec/changes/transition-oai-connector-to-kserve-plugin/design.md — Design Decision 2
  • Jira: RHIDP-15201

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions