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/ogx-tls-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-ogx-entity-provider': minor
---

Add per-provider TLS connection settings (`caData` and `skipTLSVerify`) to `OgxEntityProviderConfig` so `OgxModelEntityProvider` can fetch `/v1/models` from OGX endpoints that use a private CA or self-signed certificates.
80 changes: 80 additions & 0 deletions workspaces/boost/plugins/ogx-entity-provider/config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Red Hat, Inc.
*
* 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.
*/

/**
* Configuration schema for the OGX entity provider module.
*
* Declares the config paths read by readOgxEntityProviderConfig so that
* Backstage validates and enforces visibility on these keys even if
* the module is loaded independently of boost-backend.
*/
export interface Config {
boost?: {
/** Entity-provider-specific config (standalone deployment). */
entityProviders?: {
/** OGX entity provider connection. */
ogx?: {
/**
* Base URL of the OGX API endpoint.
* @configScope yaml-only
*/
baseUrl?: string;
/**
* API key for authenticated endpoints.
* @visibility secret
*/
apiKey?: string;
/**
* PEM-encoded CA certificate or certificate bundle used to verify the OGX endpoint.
* @visibility backend
*/
caData?: string;
/**
* Disable TLS certificate verification. Development use only.
* @configScope yaml-only
*/
skipTLSVerify?: boolean;
};
};

/** Provider module config (composed deployment). */
providers?: {
/** OGX provider connection. */
ogx?: {
/**
* Base URL of the OGX API endpoint.
* @configScope yaml-only
*/
baseUrl?: string;
/**
* API key for authenticated endpoints.
* @visibility secret
*/
apiKey?: string;
/**
* PEM-encoded CA certificate or certificate bundle used to verify the OGX endpoint.
* @visibility backend
*/
caData?: string;
/**
* Disable TLS certificate verification. Development use only.
* @configScope yaml-only
*/
skipTLSVerify?: boolean;
};
};
};
}
7 changes: 5 additions & 2 deletions workspaces/boost/plugins/ogx-entity-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@red-hat-developer-hub/backstage-plugin-ogx-entity-provider",
"version": "0.4.0",
"license": "Apache-2.0",
"configSchema": "config.d.ts",
"description": "OGX entity provider for the Backstage catalog — emits AI models and agents as catalog entities",
"main": "src/index.ts",
"types": "src/index.ts",
Expand Down Expand Up @@ -31,11 +32,13 @@
"@backstage/backend-plugin-api": "^1.9.2",
"@backstage/catalog-model": "^1.9.0",
"@backstage/plugin-catalog-node": "^2.2.2",
"@red-hat-developer-hub/backstage-plugin-boost-entity-provider-sdk": "workspace:^"
"@red-hat-developer-hub/backstage-plugin-boost-entity-provider-sdk": "workspace:^",
"undici": "^6.21.1"
},
"devDependencies": {
"@backstage/backend-test-utils": "^1.11.4",
"@backstage/cli": "^0.36.3"
"@backstage/cli": "^0.36.3",
"@backstage/config": "^1.3.2"
},
"sideEffects": false,
"scripts": {
Expand Down
116 changes: 116 additions & 0 deletions workspaces/boost/plugins/ogx-entity-provider/src/module.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Copyright Red Hat, Inc.
*
* 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.
*/

import { ConfigReader } from '@backstage/config';

import { readOgxEntityProviderConfig } from './module';

describe('readOgxEntityProviderConfig', () => {
it('reads caData and skipTLSVerify from boost.entityProviders.ogx', () => {
const config = new ConfigReader({
boost: {
entityProviders: {
ogx: {
baseUrl: 'https://ogx.example.com',
caData:
'-----BEGIN CERTIFICATE-----\nMIIBxTCC...\n-----END CERTIFICATE-----',
skipTLSVerify: true,
},
},
},
});

const result = readOgxEntityProviderConfig(config);

expect(result.baseUrl).toBe('https://ogx.example.com');
expect(result.caData).toBe(
'-----BEGIN CERTIFICATE-----\nMIIBxTCC...\n-----END CERTIFICATE-----',
);
expect(result.skipTLSVerify).toBe(true);
});

it('reads caData and skipTLSVerify from fallback boost.providers.ogx', () => {
const config = new ConfigReader({
boost: {
providers: {
ogx: {
baseUrl: 'https://ogx-fallback.example.com',
caData: 'PEM-CERT-DATA',
skipTLSVerify: false,
},
},
},
});

const result = readOgxEntityProviderConfig(config);

expect(result.baseUrl).toBe('https://ogx-fallback.example.com');
expect(result.caData).toBe('PEM-CERT-DATA');
expect(result.skipTLSVerify).toBe(false);
});

it('returns undefined for caData and skipTLSVerify when not configured', () => {
const config = new ConfigReader({
boost: {
entityProviders: {
ogx: {
baseUrl: 'http://localhost:8321',
},
},
},
});

const result = readOgxEntityProviderConfig(config);

expect(result.baseUrl).toBe('http://localhost:8321');
expect(result.caData).toBeUndefined();
expect(result.skipTLSVerify).toBeUndefined();
});

it('falls back to localhost when no OGX config is present', () => {
const config = new ConfigReader({});

const result = readOgxEntityProviderConfig(config);

expect(result.baseUrl).toBe('http://localhost:8321');
expect(result.caData).toBeUndefined();
expect(result.skipTLSVerify).toBeUndefined();
});

it('prefers entityProviders.ogx over providers.ogx', () => {
const config = new ConfigReader({
boost: {
entityProviders: {
ogx: {
baseUrl: 'https://primary.example.com',
caData: 'PRIMARY-CA',
},
},
providers: {
ogx: {
baseUrl: 'https://fallback.example.com',
caData: 'FALLBACK-CA',
},
},
},
});

const result = readOgxEntityProviderConfig(config);

expect(result.baseUrl).toBe('https://primary.example.com');
expect(result.caData).toBe('PRIMARY-CA');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ export const catalogModuleOgxEntityProvider = createBackendModule({

/**
* Read OGX entity provider configuration from app-config.yaml.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] JSDoc-style

@internal Exported for testing only. diverges from existing @internal annotations in this package which use the tag alone.

*

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] export-scope

readOgxEntityProviderConfig exported for testing with @internal annotation. Not re-exported from index.ts, so the public API is unaffected.

* @internal Exported for testing only.
*/
function readOgxEntityProviderConfig(
export function readOgxEntityProviderConfig(
config: typeof coreServices.rootConfig extends { T: infer T } ? T : never,
): OgxEntityProviderConfig {
// Try the entity-provider-specific config first
Expand All @@ -134,6 +136,8 @@ function readOgxEntityProviderConfig(
defaultAgent: epConfig.getOptionalString('defaultAgent'),
maxAgentTurns: epConfig.getOptionalNumber('maxAgentTurns'),
agents: readAgentConfigs(epConfig),
caData: epConfig.getOptionalString('caData'),
skipTLSVerify: epConfig.getOptionalBoolean('skipTLSVerify'),
};
}

Expand All @@ -147,6 +151,8 @@ function readOgxEntityProviderConfig(
defaultAgent: providerConfig.getOptionalString('defaultAgent'),
maxAgentTurns: providerConfig.getOptionalNumber('maxAgentTurns'),
agents: readAgentConfigs(providerConfig),
caData: providerConfig.getOptionalString('caData'),
skipTLSVerify: providerConfig.getOptionalBoolean('skipTLSVerify'),
};
}

Expand Down
Loading
Loading