Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for local-debuging instance of Radius #92

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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 .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ jobs:
- name: Format
if: ${{ env.CI_LINT == 'true' }}
run: yarn run format:check
- name: Link Configuration
if: ${{ env.CI_LINT == 'true' }}
run: |
yarn backstage-cli config:check --config app-config.dashboard.yaml
yarn backstage-cli config:check --config app-config.dev.yaml
yarn backstage-cli config:check --config app-config.local.yaml
- name: Build TypeScript
run: yarn run tsc
- name: Build
Expand Down
5 changes: 5 additions & 0 deletions app-config.dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ catalog:
# See https://backstage.io/docs/features/software-catalog/#adding-components-to-the-catalog for more details
# on how to get entities into the catalog.
locations: []

radius:
# Find Radius inside the Kubernetes cluster.
connection:
kind: kubernetes
17 changes: 17 additions & 0 deletions app-config.local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,26 @@ kubernetes:
clusterLocatorMethods:
- type: localKubectlProxy

proxy:
endpoints:
# Proxy to a local Radius instance.
'/radius': http://localhost:9000

radius:
# Find Radius inside the Kubernetes cluster by default.
# connection:
# kind: kubernetes
# url: ''
# Optionally uncomment the following block to use a local debug instance of Radius.
connection:
kind: direct


backend:
# Allow the backend to make requests to the local Kubernetes cluster.
reading:
allow:
- host: localhost:8001
- host: 127.0.0.1:8001
- host: localhost:9000
- host: 127.0.0.1:9000
17 changes: 17 additions & 0 deletions plugins/plugin-radius/config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export interface Config {
/**
* Radius plugin configuration
*/
radius: {
/**
* Configuration for the connection to Radius.
*/
connection: {
/**
* Kind of connection to Radius. Use `direct` for direct connection to Radius server, or `kubernetes` for connection to Radius server running in Kubernetes.
* @visibility frontend
*/
kind: "direct" | "kubernetes";
};
};
}
6 changes: 4 additions & 2 deletions plugins/plugin-radius/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@
"msw": "^1.0.0"
},
"files": [
"dist"
]
"dist",
"config.d.ts"
],
"configSchema": "config.d.ts"
}
229 changes: 125 additions & 104 deletions plugins/plugin-radius/src/api/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,131 +1,152 @@
import { makePath, makePathForId, RadiusApiImpl } from './api';
import { Connection, KubernetesConnection, RadiusApiImpl } from "./api";

describe('makePath', () => {
it('makes path for scopes', () => {
const path = makePath({ scopes: [{ type: 'radius', value: 'local' }] });
expect(path).toEqual(makePathForId('/planes/radius/local'));
});
it('makes path for multiple scopes', () => {
const path = makePath({
scopes: [
{ type: 'radius', value: 'local' },
{
type: 'resourceGroups',
value: 'test-group',
describe("KubernetesConnection", () => {
it("selectCluster returns first cluster", async () => {
const connection = new KubernetesConnection({
getClusters: async () => [
{ name: "test-cluster1", authProvider: "test" },
{
name: "test-cluster2",
authProvider: "test",
},
],
proxy: async () => {
throw new Error("not implemented");
},
],
});
expect(path).toEqual(
makePathForId('/planes/radius/local/resourceGroups/test-group'),
);
});
expect(await connection.selectCluster()).toEqual("test-cluster1");
});
it('makes path for scope list', () => {
const path = makePath({
scopes: [
{ type: 'radius', value: 'local' },
{
type: 'resourceGroups',
})

describe("RadiusApi", () => {
const makeApi = (
mocks?: {
connection?: Connection;
},
) => {
return new RadiusApiImpl(
mocks?.connection ?? {
send() {
throw new Error(`Not implemented`);
},
],
},
);
};

describe("makePath", () => {
const api = makeApi();

it("makes path for scopes", () => {
const path = api.makePath({
scopes: [{ type: "radius", value: "local" }],
});
expect(path).toEqual(api.makePathForId("/planes/radius/local"));
});
expect(path).toEqual(makePathForId('/planes/radius/local/resourceGroups'));
});
it('makes path for scope action', () => {
const path = makePath({
scopes: [{ type: 'radius', value: 'local' }],
action: 'action',
it("makes path for multiple scopes", () => {
const path = api.makePath({
scopes: [
{ type: "radius", value: "local" },
{
type: "resourceGroups",
value: "test-group",
},
],
});
expect(path).toEqual(
api.makePathForId("/planes/radius/local/resourceGroups/test-group"),
);
});
expect(path).toEqual(makePathForId('/planes/radius/local/action'));
});
it('makes path for resource type without name', () => {
const path = makePath({
scopes: [{ type: 'radius', value: 'local' }],
type: 'Applications.Core/applications',
it("makes path for scope list", () => {
const path = api.makePath({
scopes: [
{ type: "radius", value: "local" },
{
type: "resourceGroups",
},
],
});
expect(path).toEqual(
api.makePathForId("/planes/radius/local/resourceGroups"),
);
});
expect(path).toEqual(
makePathForId(
'/planes/radius/local/providers/Applications.Core/applications',
),
);
});
it('makes path for resource type with name', () => {
const path = makePath({
scopes: [{ type: 'radius', value: 'local' }],
type: 'Applications.Core/applications',
name: 'test-app',
it("makes path for scope action", () => {
const path = api.makePath({
scopes: [{ type: "radius", value: "local" }],
action: "action",
});
expect(path).toEqual(api.makePathForId("/planes/radius/local/action"));
});
expect(path).toEqual(
makePathForId(
'/planes/radius/local/providers/Applications.Core/applications/test-app',
),
);
});
it('makes path for resource type with name and action', () => {
const path = makePath({
scopes: [{ type: 'radius', value: 'local' }],
type: 'Applications.Core/applications',
name: 'test-app',
action: 'restart',
it("makes path for resource type without name", () => {
const path = api.makePath({
scopes: [{ type: "radius", value: "local" }],
type: "Applications.Core/applications",
});
expect(path).toEqual(
api.makePathForId(
"/planes/radius/local/providers/Applications.Core/applications",
),
);
});
expect(path).toEqual(
makePathForId(
'/planes/radius/local/providers/Applications.Core/applications/test-app/restart',
),
);
});
});

describe('RadiusApi', () => {
it('selectCluster returns first cluster', async () => {
const api = new RadiusApiImpl({
getClusters: async () => [
{ name: 'test-cluster1', authProvider: 'test' },
{
name: 'test-cluster2',
authProvider: 'test',
},
],
proxy: async () => {
throw new Error('not implemented');
},
it("makes path for resource type with name", () => {
const path = api.makePath({
scopes: [{ type: "radius", value: "local" }],
type: "Applications.Core/applications",
name: "test-app",
});
expect(path).toEqual(
api.makePathForId(
"/planes/radius/local/providers/Applications.Core/applications/test-app",
),
);
});
it("makes path for resource type with name and action", () => {
const path = api.makePath({
scopes: [{ type: "radius", value: "local" }],
type: "Applications.Core/applications",
name: "test-app",
action: "restart",
});
expect(path).toEqual(
api.makePathForId(
"/planes/radius/local/providers/Applications.Core/applications/test-app/restart",
),
);
});
// eslint-disable-next-line dot-notation
expect(await api['selectCluster']()).toEqual('test-cluster1');
});
it('makeRequest handles errors', async () => {
const api = new RadiusApiImpl({
getClusters: async () => {
throw new Error('not implemented');

it("makeRequest handles errors", async () => {
const api = makeApi({
connection: {
send: async () =>
Promise.resolve(new Response("test", { status: 404 })),
},
proxy: async () => Promise.resolve(new Response('test', { status: 404 })),
});
// eslint-disable-next-line dot-notation
await expect(api['makeRequest']('cluster', 'path')).rejects.toThrow(
'Request failed: 404:\n\ntest',
await expect(api["makeRequest"]("path")).rejects.toThrow(
"Request failed: 404:\n\ntest",
);
});
it('makeRequest expects JSON', async () => {
const api = new RadiusApiImpl({
getClusters: async () => {
throw new Error('not implemented');
it("makeRequest expects JSON", async () => {
const api = makeApi({
connection: {
send: async () => Promise.resolve(new Response("test")),
},
proxy: async () => Promise.resolve(new Response('test')),
});
// eslint-disable-next-line dot-notation
await expect(api['makeRequest']('cluster', 'path')).rejects.toThrow(
'invalid json response body at reason: Unexpected token \'e\', "test" is not valid JSON',
await expect(api["makeRequest"]("path")).rejects.toThrow(
"Request was not json: 200:\n\ntest",
);
});
it('makeRequest parses JSON', async () => {
const api = new RadiusApiImpl({
getClusters: async () => {
throw new Error('not implemented');
it("makeRequest parses JSON", async () => {
const api = makeApi({
connection: {
send: async () =>
Promise.resolve(new Response('{ "message": "test" }')),
},
proxy: async () => Promise.resolve(new Response('{ "message": "test" }')),
});
// eslint-disable-next-line dot-notation
await expect(api['makeRequest']('cluster', 'path')).resolves.toEqual({
message: 'test',
await expect(api["makeRequest"]("path")).resolves.toEqual({
message: "test",
});
});
});
Loading
Loading