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
6 changes: 6 additions & 0 deletions workspaces/dcm/.changeset/rename-dcm-api-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@red-hat-developer-hub/backstage-plugin-dcm-backend': patch
---

Rename DCM_API_GATEWAY_URL to DCM_API_URL. Legacy env var and
dcm.apiGatewayUrl config remain supported during migration.
2 changes: 2 additions & 0 deletions workspaces/dcm/app-config.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ backend:
connection: ':memory:'

dcm:
apiUrl: ${DCM_API_URL:-}
# Legacy env var; kept until deploy configs switch to DCM_API_URL.
apiGatewayUrl: ${DCM_API_GATEWAY_URL:-}
ssoBaseUrl: ${DCM_SSO_BASE_URL:-}
clientId: ${DCM_CLIENT_ID:-}
Expand Down
4 changes: 2 additions & 2 deletions workspaces/dcm/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ dcm:
- security-baseline
- compliance-pci
- audit-logging
# Base URL of the DCM API Gateway. All three API services (catalog,
# Base URL of the DCM control plane. All three API services (catalog,
# policy-manager, providers) are routed through this single endpoint.
# Override in app-config.local.yaml for local development.
# apiGatewayUrl: https://your-api-gateway.example.com
# apiUrl: https://your-control-plane.example.com
#
# SSO credentials for the backend to obtain a bearer token:
# ssoBaseUrl: https://sso.redhat.com
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
dcm:
# Base URL of the DCM API Gateway (required).
# Base URL of the DCM control plane (required).
apiUrl: ${DCM_API_URL}
# Legacy env var; kept until deploy configs switch to DCM_API_URL.
apiGatewayUrl: ${DCM_API_GATEWAY_URL}

# SSO configuration for the backend to obtain bearer tokens via
Expand Down
17 changes: 14 additions & 3 deletions workspaces/dcm/plugins/dcm-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,24 @@
export interface Config {
dcm: {
/**
* Base URL of the DCM API Gateway.
* Base URL of the DCM control plane.
*
* All API services (catalog, policy-manager, providers) are routed
* through this single gateway. The backend appends `/api/v1alpha1/<path>`
* through this endpoint. The backend appends `/api/v1alpha1/<path>`
* to construct the upstream URL.
*
* @example "http://localhost:9080"
* @example "http://localhost:8080"
* @visibility backend
*/
apiUrl?: string;

/**
* Base URL of the DCM API Gateway.
*
* @deprecated Use `apiUrl` instead. This key remains supported for
* backward compatibility during migration.
*
* @example "http://localhost:9080" (legacy API gateway port)
* @visibility backend
*/
apiGatewayUrl?: string;
Expand Down
16 changes: 8 additions & 8 deletions workspaces/dcm/plugins/dcm-backend/src/routes/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('createDcmProxy', () => {
fetchSpy?.mockRestore();
});

it('returns 503 when dcm.apiGatewayUrl is not configured', async () => {
it('returns 503 when dcm.apiUrl is not configured', async () => {
const app = makeApp({ dcm: { clientId: 'id', clientSecret: 'secret' } });

const res = await request(app).get('/proxy/providers');
Expand All @@ -77,7 +77,7 @@ describe('createDcmProxy', () => {

const app = makeApp({
dcm: {
apiGatewayUrl: 'https://gateway.example.com',
apiUrl: 'https://control-plane.example.com',
clientId: 'id',
clientSecret: 'secret',
},
Expand All @@ -101,7 +101,7 @@ describe('createDcmProxy', () => {

const app = makeApp({
dcm: {
apiGatewayUrl: 'https://gateway.example.com',
apiUrl: 'https://control-plane.example.com',
clientId: 'id',
clientSecret: 'secret',
},
Expand All @@ -111,7 +111,7 @@ describe('createDcmProxy', () => {

expect(res.status).toBe(502);
expect(res.body).toMatchObject({
error: expect.stringContaining('DCM API gateway'),
error: expect.stringContaining('DCM API'),
});
});

Expand All @@ -134,7 +134,7 @@ describe('createDcmProxy', () => {

const app = makeApp({
dcm: {
apiGatewayUrl: 'https://gateway.example.com',
apiUrl: 'https://control-plane.example.com',
clientId: 'id',
clientSecret: 'secret',
},
Expand Down Expand Up @@ -171,7 +171,7 @@ describe('createDcmProxy', () => {

const app = makeApp({
dcm: {
apiGatewayUrl: 'https://gateway.example.com',
apiUrl: 'https://control-plane.example.com',
clientId: 'id',
clientSecret: 'secret',
},
Expand Down Expand Up @@ -206,7 +206,7 @@ describe('createDcmProxy', () => {

const app = makeApp({
dcm: {
apiGatewayUrl: 'https://gateway.example.com',
apiUrl: 'https://control-plane.example.com',
clientId: 'id',
clientSecret: 'secret',
},
Expand All @@ -227,7 +227,7 @@ describe('createDcmProxy', () => {
expect(JSON.parse(upstreamCall[1].body)).toEqual(patch);
});

it('handles 204 No Content upstream responses without a body', async () => {
it('falls back to legacy dcm.apiGatewayUrl and handles 204 No Content', async () => {
fetchSpy = jest
.spyOn(globalThis, 'fetch')
.mockResolvedValueOnce(TOKEN_RESPONSE)
Expand Down
24 changes: 12 additions & 12 deletions workspaces/dcm/plugins/dcm-backend/src/routes/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,35 @@ import { getTokenFromApi } from '../util/tokenUtil';
const API_BASE_PATH = '/api/v1alpha1';

/**
* Proxies all `ALL /proxy/*` requests to the DCM API Gateway.
* Proxies all `ALL /proxy/*` requests to the DCM control plane.
*
* The wildcard path segment is appended to:
* `{dcm.apiGatewayUrl}/api/v1alpha1/<wildcardPath>`
* `{dcm.apiUrl}/api/v1alpha1/<wildcardPath>`
*
* An SSO bearer token is injected automatically via `tokenUtil`.
*/
export function createDcmProxy(options: RouterOptions) {
return async (req: Request, res: Response): Promise<void> => {
const { logger, config } = options;

const apiGatewayUrl = config.getOptionalString('dcm.apiGatewayUrl');
if (!apiGatewayUrl) {
// Prefer dcm.apiUrl (DCM_API_URL); legacy apiGatewayUrl eases migration.
const apiUrl =
config.getOptionalString('dcm.apiUrl') ??
config.getOptionalString('dcm.apiGatewayUrl');
if (!apiUrl) {
logger.error(
'dcm.apiGatewayUrl is not configured — cannot proxy DCM API requests.',
'dcm.apiUrl is not configured — cannot proxy DCM API requests.',
);
res
.status(503)
.json({ error: 'DCM API gateway is not configured on the server.' });
.json({ error: 'DCM API is not configured on the server.' });
return;
}

// req.params[0] is the captured wildcard after /proxy/
const wildcardPath = (req.params as Record<string, string>)[0] ?? '';

const targetUrl = new URL(
`${API_BASE_PATH}/${wildcardPath}`,
apiGatewayUrl,
);
const targetUrl = new URL(`${API_BASE_PATH}/${wildcardPath}`, apiUrl);

// Forward all query parameters from the original request
const incomingParams = new URLSearchParams(
Expand Down Expand Up @@ -80,7 +80,7 @@ export function createDcmProxy(options: RouterOptions) {

// Only attach the Authorization header when an SSO token was obtained.
// When clientId/clientSecret are not configured the token is empty and
// the request is forwarded without auth (open/unauthenticated gateway).
// the request is forwarded without auth (open/unauthenticated API).
if (tokenResult.accessToken) {
requestHeaders.Authorization = `Bearer ${tokenResult.accessToken}`;
}
Expand All @@ -104,7 +104,7 @@ export function createDcmProxy(options: RouterOptions) {
});
} catch (err) {
logger.error(`DCM proxy: upstream fetch failed — ${err}`);
res.status(502).json({ error: 'Failed to reach the DCM API gateway.' });
res.status(502).json({ error: 'Failed to reach the DCM API.' });
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { DcmBaseClient } from './DcmBaseClient';
*
* All requests are sent to `/api/dcm/proxy/<path>` where the backend
* strips the `/proxy` prefix and forwards to:
* `{dcm.apiGatewayUrl}/api/v1alpha1/<path>`
* `{dcm.apiUrl}/api/v1alpha1/<path>`
*
* @public
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const PLUGIN_ID = 'dcm';
* Base class shared by all DCM API clients.
*
* Routes every call through the dcm-backend secure proxy:
* `GET /api/dcm/proxy/<path>` → `{dcm.apiGatewayUrl}/api/v1alpha1/<path>`
* `GET /api/dcm/proxy/<path>` → `{dcm.apiUrl}/api/v1alpha1/<path>`
*
* @public
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { DcmBaseClient } from './DcmBaseClient';
*
* All requests are sent to `/api/dcm/proxy/<path>` where the backend
* strips the `/proxy` prefix and forwards to:
* `{dcm.apiGatewayUrl}/api/v1alpha1/<path>`
* `{dcm.apiUrl}/api/v1alpha1/<path>`
*
* @public
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { DcmBaseClient } from './DcmBaseClient';
*
* All requests are sent to `/api/dcm/proxy/<path>` where the backend
* strips the `/proxy` prefix and forwards to:
* `{dcm.apiGatewayUrl}/api/v1alpha1/<path>`
* `{dcm.apiUrl}/api/v1alpha1/<path>`
*
* @public
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import type {
* Default implementation of {@link ResourcesApi}.
*
* Routes calls through the dcm-backend secure proxy to the
* `/service-type-instances` endpoint of the DCM API Gateway.
* `/service-type-instances` endpoint of the DCM control plane.
*
* @public
*/
Expand Down
Loading