Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions workspaces/boost/.changeset/connector-utils-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-boost-connector-utils': minor
---

Initial release of shared utilities for Boost AI catalog connector entity providers. Provides CA bundle resolution, fault isolation wrappers, enable/disable config guards, and startup validation for air-gapped deployments.
162 changes: 162 additions & 0 deletions workspaces/boost/examples/app-config.connectors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Reference App-Config for AI Catalog Connectors
#
# This file demonstrates the configurable fields for all three AI catalog
# connectors (MCP Registry, RHOAI MCP Catalog, OCI Skill Registry).
# Copy the relevant sections into your app-config.yaml and adjust values
# for your deployment environment.
#
# Config root: ai-catalog.providers.<connectorId>
# Each connector reads its own Config subtree — shared utilities do not
# hard-code config paths.
#
# See also: workspaces/boost/plugins/boost-connector-utils/README.md

# --------------------------------------------------------------------------
# Internet-Connected Deployment (default)
# --------------------------------------------------------------------------

ai-catalog:
providers:
# --- MCP Registry Connector ---
mcpRegistry:
# Whether this connector is registered at startup (default: true)
enabled: true

# MCP Registry endpoint URL (required, must be HTTPS)
# Falls back to public registry.modelcontextprotocol.io when omitted
endpoint: https://registry.modelcontextprotocol.io

# TLS configuration for custom CA bundles (optional)
# Use when the registry has a self-signed or internal CA certificate
tls:
# Option 1: Path to a PEM file mounted from a K8s Secret/ConfigMap
# caFile: /etc/ssl/certs/custom-ca-bundle.crt

# Option 2: PEM content from an environment variable
# The env var is populated by K8s via Secret mount
# caSecret:
# $env: MCP_REGISTRY_CA_BUNDLE

# Registry credentials via mounted K8s Secret (optional)
# Values MUST use $env references — plaintext is rejected at startup
auth:
token:
$env: MCP_REGISTRY_TOKEN

# Sync schedule (optional)
schedule:
frequency: { minutes: 30 }
timeout: { minutes: 10 }

# --- RHOAI MCP Catalog Connector ---
rhoai:
mcpCatalog:
# Whether this connector is registered at startup (default: true)
enabled: true

# Cross-cluster RHOAI MCP catalog API endpoint (required, must be HTTPS)
endpoint: https://mcp-catalog.rhoai-cluster.example.com

# TLS configuration for custom CA bundles (optional)
tls:
# Path to a PEM file mounted from a K8s Secret/ConfigMap
caFile: /etc/rhdh/ca-bundles/rhoai-ca.pem

# RHOAI credentials via mounted K8s Secret (required)
# Values MUST use $env references — plaintext is rejected at startup
auth:
clientId:
$env: RHOAI_CLIENT_ID
clientSecret:
$env: RHOAI_CLIENT_SECRET

# Sync schedule (optional)
schedule:
frequency: { minutes: 15 }
timeout: { minutes: 5 }

# --- OCI Skill Registry Connector ---
ociSkill:
# Whether this connector is registered at startup (default: true)
enabled: true

# Multiple OCI registries can be configured
registries:
- # Public or internal OCI registry URL (required, must be HTTPS)
url: https://quay.io
# OCI namespace containing skill artifacts
namespace: skills
# Path to K8s pull secret in Docker config.json format (optional)
pullSecretPath: /var/run/secrets/quay-pull-secret/.dockerconfigjson

- url: https://harbor.internal.example.com
namespace: ai-assets
pullSecretPath: /var/run/secrets/harbor-pull-secret/.dockerconfigjson
# Per-registry TLS configuration
tls:
caFile: /etc/ssl/certs/harbor-ca.crt

# Discovery settings (optional)
discovery:
# Parallel manifest fetch concurrency (default: 20)
concurrency: 20

# Sync schedule (optional)
schedule:
frequency: { minutes: 30 }
timeout: { minutes: 15 }

# --------------------------------------------------------------------------
# Air-Gapped Deployment Variant
# --------------------------------------------------------------------------
# Use this variant when the RHDH instance has no internet access.
# All endpoints point to internal mirrors. No public endpoint traffic
# will occur.
#
# Uncomment and replace the section above with this configuration:
#
# ai-catalog:
# providers:
# # --- MCP Registry (internal mirror) ---
# mcpRegistry:
# enabled: true
# endpoint: https://registry.internal.example.com
# tls:
# caFile: /etc/ssl/certs/internal-ca-bundle.pem
# auth:
# token:
# $env: MCP_REGISTRY_TOKEN
# schedule:
# frequency: { minutes: 30 }
# timeout: { minutes: 10 }
#
# # --- RHOAI MCP Catalog (internal cluster) ---
# rhoai:
# mcpCatalog:
# enabled: true
# endpoint: https://mcp-catalog.rhoai.internal.example.com
# tls:
# caFile: /etc/ssl/certs/internal-ca-bundle.pem
# auth:
# clientId:
# $env: RHOAI_CLIENT_ID
# clientSecret:
# $env: RHOAI_CLIENT_SECRET
# schedule:
# frequency: { minutes: 15 }
# timeout: { minutes: 5 }
#
# # --- OCI Skill Registry (internal Harbor/Quay mirror) ---
# ociSkill:
# enabled: true
# registries:
# - url: https://harbor.internal.example.com
# namespace: ai-skills
# pullSecretPath: /var/run/secrets/harbor-pull-secret/.dockerconfigjson
# tls:
# caFile: /etc/ssl/certs/internal-ca-bundle.pem
# discovery:
# concurrency: 10
# schedule:
# frequency: { minutes: 30 }
# timeout: { minutes: 15 }
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ The shared infrastructure lives in a standalone utility package (`@red-hat-devel
plugins/boost-connector-utils/
├── package.json # @red-hat-developer-hub/backstage-plugin-boost-connector-utils
├── src/
│ ├── index.ts # Exports: loadCaBundle, createHttpsAgent, createProviderWrapper, createSafeRefresh, isConnectorEnabled, ConnectorErrorContext
│ ├── index.ts # Exports: loadCaBundle, createHttpsAgent, createProviderWrapper, createSafeRefresh, classifyConnectorError, isConnectorEnabled, validateConnectorStartupConfig, ConnectorEntityProvider, ConnectorErrorContext, ValidateConnectorStartupConfigOptions
│ ├── ca-bundle.ts # CA bundle loading logic
│ ├── fault-isolation.ts # Provider wrapper with error handling
│ └── config.ts # Enable/disable guard
Expand Down Expand Up @@ -71,14 +71,17 @@ ai-catalog:
**Function signature:**

```typescript
function loadCaBundle(connectorConfig: Config): Buffer | undefined;
function loadCaBundle(
connectorConfig: Config,
logger: LoggerService,
): Buffer | undefined;
```

The caller passes the Config subtree that contains the `tls` block. This allows each connector to resolve its own config nesting before calling the shared utility:

- MCP Registry: `loadCaBundle(config.getConfig('ai-catalog.providers.mcpRegistry'))`
- RHOAI MCP Catalog: `loadCaBundle(config.getConfig('ai-catalog.providers.rhoai.mcpCatalog'))`
- OCI per-registry: `loadCaBundle(registryConfig)` where `registryConfig` is the per-registry Config node
- MCP Registry: `loadCaBundle(config.getConfig('ai-catalog.providers.mcpRegistry'), logger)`
- RHOAI MCP Catalog: `loadCaBundle(config.getConfig('ai-catalog.providers.rhoai.mcpCatalog'), logger)`
- OCI per-registry: `loadCaBundle(registryConfig, logger)` where `registryConfig` is the per-registry Config node

**Behavior:**

Expand All @@ -94,7 +97,7 @@ The caller passes the Config subtree that contains the `tls` block. This allows

```typescript
const connectorConfig = config.getConfig('ai-catalog.providers.mcpRegistry');
const caBundle = loadCaBundle(connectorConfig);
const caBundle = loadCaBundle(connectorConfig, logger);
const agent = caBundle ? new https.Agent({ ca: caBundle }) : undefined;

const client = axios.create({ httpsAgent: agent });
Expand All @@ -121,12 +124,13 @@ Backstage already provides entity data isolation per provider via entity buckets
```typescript
// In boost-connector-utils/src/fault-isolation.ts
export function createProviderWrapper(
provider: EntityProvider,
provider: ConnectorEntityProvider,
logger: LoggerService,
): EntityProvider {
ctx?: { endpoint?: string },
): ConnectorEntityProvider {
return {
getProviderName: () => provider.getProviderName(),
async connect(connection: EntityProviderConnection): Promise<void> {
async connect(connection: unknown): Promise<void> {
try {
await provider.connect(connection);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Duplicating CA/TLS handling, error logging, and enable/disable config across eac

### CA Bundle Resolution Utility

- `loadCaBundle(connectorConfig: Config): Buffer | undefined` function — caller passes the Config subtree containing the `tls` block
- Caller resolves config nesting before calling: e.g., `config.getConfig('ai-catalog.providers.mcpRegistry')` for MCP, `config.getConfig('ai-catalog.providers.rhoai.mcpCatalog')` for RHOAI, per-registry Config node for OCI
- `loadCaBundle(connectorConfig: Config, logger: LoggerService): Buffer | undefined` function — caller passes the Config subtree containing the `tls` block
- Caller resolves config nesting before calling: e.g., `config.getConfig('ai-catalog.providers.mcpRegistry'), logger` for MCP, `config.getConfig('ai-catalog.providers.rhoai.mcpCatalog'), logger` for RHOAI, per-registry Config node for OCI
- Reads CA bundles from K8s Secret/ConfigMap mounts or direct file paths
- Creates `https.Agent` with custom CA for HTTP client injection
- Handles missing/invalid CA gracefully: log warning, return undefined, don't crash provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## 1. CA Bundle Resolution Utility (P0) — RHIDP-15329

- [ ] 1.1 Create `@red-hat-developer-hub/backstage-plugin-boost-connector-utils` package with `package.json`, TypeScript config, and README
- [ ] 1.2 Define `loadCaBundle(connectorConfig: Config): Buffer | undefined` function signature — caller passes the Config subtree containing the `tls` block
- [ ] 1.2 Define `loadCaBundle(connectorConfig: Config, logger: LoggerService): Buffer | undefined` function signature — caller passes the Config subtree containing the `tls` block
- [ ] 1.3 Implement caFile resolution — read CA from `tls.caFile` within the provided Config subtree
- [ ] 1.4 Implement caSecret resolution — read CA from `tls.caSecret.$env` within the provided Config subtree
- [ ] 1.5 Add per-connector config isolation — each connector resolves its own Config nesting before calling `loadCaBundle()` (e.g., MCP passes `config.getConfig('ai-catalog.providers.mcpRegistry')`, RHOAI passes `config.getConfig('ai-catalog.providers.rhoai.mcpCatalog')`, OCI passes per-registry Config node)
Expand Down
1 change: 1 addition & 0 deletions workspaces/boost/plugins/boost-backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@red-hat-developer-hub/backstage-plugin-boost",
"@red-hat-developer-hub/backstage-plugin-boost-backend",
"@red-hat-developer-hub/backstage-plugin-boost-common",
"@red-hat-developer-hub/backstage-plugin-boost-connector-utils",
"@red-hat-developer-hub/backstage-plugin-boost-node",
"@red-hat-developer-hub/backstage-plugin-boost-responses-api-toolkit",
"@red-hat-developer-hub/backstage-plugin-boost-toolscope"
Expand Down
1 change: 1 addition & 0 deletions workspaces/boost/plugins/boost-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@red-hat-developer-hub/backstage-plugin-boost",
"@red-hat-developer-hub/backstage-plugin-boost-backend",
"@red-hat-developer-hub/backstage-plugin-boost-common",
"@red-hat-developer-hub/backstage-plugin-boost-connector-utils",
"@red-hat-developer-hub/backstage-plugin-boost-node",
"@red-hat-developer-hub/backstage-plugin-boost-responses-api-toolkit",
"@red-hat-developer-hub/backstage-plugin-boost-toolscope"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
Loading
Loading