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:
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.
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
The
kserve-kubeflow-connector-backendplugin creates its K8s clients viaKubeConfig.loadFromDefault(), which reads from~/.kube/config(e.g., afteroc login) or theKUBECONFIGenv var, and optionally overrides the token with aK8S_TOKENenvironment variable. This works for local development but does not follow Backstage conventions — platform engineers expect to configure K8s cluster access throughapp-config.yaml, not environment variables. The OCM plugin provides the reference pattern: cluster credentials either directly in the provider config or via akubernetesPluginRefthat references a cluster defined in the Backstage kubernetes plugin'skubernetes.clusterLocatorMethodsconfig.Additionally, two type-level issues have accumulated:
ModelCatalogConfig.baseUrlin the entity provider is unused in practice — if the BackstageDiscoveryServicecannot resolve the connector, nothing else works either (auth tokens, endpoint calls), so the fallback adds complexity without value.ConnectorConfigandReconcilerConfigin the connector plugin are redundant —ConnectorConfigexists solely to be unpacked intoReconcilerConfigfive lines later insetupInformer.Tasks
From
openspec/changes/k8s-config-migration/tasks.md:Section 1 — Remove baseUrl from Entity Provider:
plugins/catalog-backend-module-model-catalog/src/providers/types.ts, removebaseUrlfromModelCatalogConfigplugins/catalog-backend-module-model-catalog/src/providers/config.ts, removebaseUrlreading fromreadModelCatalogApiEntityConfigplugins/catalog-backend-module-model-catalog/src/providers/ModelCatalogResourceEntityProvider.ts: removebaseUrlfield and simplifyrun()to useconst url = await this.discovery.getBaseUrl(this.name)directlyplugins/catalog-backend-module-model-catalog/config.d.ts, removebaseUrl?: stringfrom the connector-level fieldsyarn tscpassesSection 2 — Merge ConnectorConfig into ReconcilerConfig:
plugins/kserve-kubeflow-connector-backend/src/services/types.ts, changedefaultLifecycleanddefaultOwnerfrom requiredstringto optional?: stringonReconcilerConfigplugins/kserve-kubeflow-connector-backend/src/services/InformerService.ts: removeConnectorConfiginterface and export; updatesetupInformersignature from(connectorConfig?: ConnectorConfig)to(config: ReconcilerConfig, logger: LoggerService); apply defaults fordefaultOwneranddefaultLifecycleinsidesetupInformerplugins/kserve-kubeflow-connector-backend/src/plugin.ts: removeConnectorConfigimport, importReconcilerConfigfrom./services/types, buildReconcilerConfigdirectly, passlogger(frominitdeps) tosetupInformerConnectorConfigyarn tscpassesSection 3 — Add K8s Connection Fields to ReconcilerConfig:
clusterName?,url?,serviceAccountToken?,skipTLSVerify?,caData?toReconcilerConfigintypes.tsyarn tscpassesSection 4 — Implement KubeConfig from Config Fields:
setupInformer, build KubeConfig vialoadFromOptions()whenconfig.url && config.serviceAccountTokenare present; log partial config warning when only one is present; fall back toloadFromDefault()otherwiseconfig.serviceAccountTokenwhen config fields present, keep existing extraction logic +K8S_TOKENoverride forloadFromDefault()path.loadFromDefault()also supportsKUBECONFIGenv var and~/.kube/configfromoc loginconfig.serviceAccountToken(NOTconfig.k8sToken). Removek8sTokenfield entirely —serviceAccountTokenis the single token field. Update all code readingconfig.k8sTokento readconfig.serviceAccountTokenyarn tscpassesSection 5 — Implement kubernetesPluginRef Lookup in plugin.ts:
Note: Use
safeGetOptionalString(see AGENTS.mdConfigReader getOptionalString() edge case) for all optional string config reads. Duplicate the helper locally — do NOT import the url-reader variant which returns''on error.kubernetesPluginRefin cluster sub-configkubernetes.clusterLocatorMethodsusing amatchedflag pattern; on match extracturl,serviceAccountToken,skipTLSVerify,caData; post-match validate BOTHurlANDserviceAccountToken— if either is missing, log warning with field status, clear all stale K8s fields, fall through to direct configauthProviderfield — if set and notserviceAccount, log warning but proceed!reconcilerConfig.url || !reconcilerConfig.serviceAccountToken)yarn tscpassesSection 6 — Update config.d.ts Schema:
url,serviceAccountToken,skipTLSVerify,caData,kubernetesPluginRef) with proper@visibilityannotations to the[clusterKey: string]union inconfig.d.tsbaseUrlremoval persisted from Task 1.4yarn tscpassesSection 7 — Update app-config.yaml:
cluster-1sectionkubernetesPluginRefalternativekubernetessection for use withkubernetesPluginRefSection 8 — Add K8s RBAC Example YAML:
examples/k8s-rbac.yamlwith ServiceAccount, ClusterRole (InferenceServices: get/watch/list; Routes: get/list; ServiceAccounts: get/list), and ClusterRoleBindingInformerService.tsandCatalog.tsSection 9 — Replace console.log with logger in setupInformer:
console.log/console.errorcalls insidesetupInformerwithlogger.info/logger.error/logger.debug. Only change functions that receive the logger — leave informer event handlers as-isyarn tscpassesSection 10 — Verification:
yarn tscpasses with no errorsyarn build:allsucceedsyarn test:all)yarn prettier)yarn lint:all)app-config.yamland connects to clusterkubernetesPluginRefand connects to clusterloadFromDefault()) — verify with~/.kube/configfromoc loginKUBECONFIGenv var pointing to a custom kubeconfig fileK8S_TOKENenv var still works as token override when usingloadFromDefault()Specifications
openspec/changes/k8s-config-migration/proposal.md— scope, capabilities added/modified, non-goalsopenspec/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 capabilitiesopenspec/changes/transition-oai-connector-to-kserve-plugin/design.md— Design Decision 2