Skip to content

Commit

Permalink
feat(kiali): show kiali information in header (#630)
Browse files Browse the repository at this point in the history
  • Loading branch information
aljesusg authored Aug 28, 2023
1 parent 5abe476 commit b9a83b3
Show file tree
Hide file tree
Showing 28 changed files with 774 additions and 373 deletions.
318 changes: 155 additions & 163 deletions plugins/kiali-backend/src/clients/KialiAPIConnector.ts

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions plugins/kiali-backend/src/clients/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,15 @@ export class KialiFetcher {
return null;
}

handleUnsuccessfulResponse(res: AxiosError): KialiFetchError {
handleUnsuccessfulResponse(
res: AxiosError,
endpoint?: string,
): KialiFetchError {
const message = res.message;
const codeMessage = res.code ? `status (${res.code}) ` : '';
const url = res.config?.url || '';
const url = endpoint || res.config?.url || '';
const urlMessage = url ? `when fetching "${url}" in "Kiali";` : '';
this.logger.warn(`Error response axios: ${JSON.stringify(res)}`);
this.logger.warn(
`Received ${res.status} ${codeMessage} ${urlMessage} body=[${message}]`,
);
Expand Down
28 changes: 18 additions & 10 deletions plugins/kiali-backend/src/filters/byAnnotation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Entity } from '@backstage/catalog-model';

import {
KUBERNETES_ANNOTATION,
KUBERNETES_LABEL_SELECTOR,
KUBERNETES_NAMESPACE,
Namespace,
NamespaceInfo,
} from '@janus-idp/backstage-plugin-kiali-common';

const filterById = (ns: Namespace[], value: string): Namespace[] => {
Expand All @@ -23,33 +26,38 @@ const filterBySelector = (ns: Namespace[], value: string): Namespace[] => {
);
};

const filterByNs = (ns: Namespace[], value: string): Namespace[] => {
const filterByNs = (ns: Namespace[], value: string): NamespaceInfo[] => {
const values = value.split(',');
return ns.filter(n => values.includes(n.name));
};

export const filterNsByAnnotation = (
ns: Namespace[],
annotation?: { [key: string]: string },
entity: Entity,
): Namespace[] => {
if (!annotation) {
return ns;
const annotations = entity.metadata.annotations;
if (!annotations) {
return [];
}

let nsFilter = ns;
nsFilter = annotation[KUBERNETES_ANNOTATION]
nsFilter = annotations[KUBERNETES_ANNOTATION]
? filterById(
nsFilter,
decodeURIComponent(annotation[KUBERNETES_ANNOTATION]),
decodeURIComponent(annotations[KUBERNETES_ANNOTATION]),
)
: nsFilter;
nsFilter = annotation[KUBERNETES_LABEL_SELECTOR]
nsFilter = annotations[KUBERNETES_LABEL_SELECTOR]
? filterBySelector(
nsFilter,
decodeURIComponent(annotation[KUBERNETES_LABEL_SELECTOR]),
decodeURIComponent(annotations[KUBERNETES_LABEL_SELECTOR]),
)
: nsFilter;
nsFilter = annotation[KUBERNETES_NAMESPACE]
? filterByNs(nsFilter, decodeURIComponent(annotation[KUBERNETES_NAMESPACE]))
nsFilter = annotations[KUBERNETES_NAMESPACE]
? filterByNs(
nsFilter,
decodeURIComponent(annotations[KUBERNETES_NAMESPACE]),
)
: nsFilter;
return nsFilter;
};
Expand Down
10 changes: 9 additions & 1 deletion plugins/kiali-backend/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { loggerToWinstonLogger } from '@backstage/backend-common';
import {
HostDiscovery,
loggerToWinstonLogger,
} from '@backstage/backend-common';
import {
coreServices,
createBackendPlugin,
} from '@backstage/backend-plugin-api';
import { CatalogClient } from '@backstage/catalog-client';
import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha';

import { createRouter } from './service/router';
Expand All @@ -23,9 +27,13 @@ export const kialiPlugin = createBackendPlugin({
},
async init({ http, logger, config }) {
const winstonLogger = loggerToWinstonLogger(logger);
const catalogApi = new CatalogClient({
discoveryApi: HostDiscovery.fromConfig(config),
});
const router = await createRouter({
logger: winstonLogger,
config,
catalogApi,
});
http.use(router);
},
Expand Down
49 changes: 11 additions & 38 deletions plugins/kiali-backend/src/service/router.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { HostDiscovery } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { ConfigReader } from '@backstage/config';

import express from 'express';
Expand All @@ -15,6 +17,9 @@ const server = setupServer(...handlers);

const kialiURL = 'https://localhost:4000';
const MockConfig = {
backend: {
baseUrl: 'http://localhost:7007',
},
catalog: {
providers: {
kiali: {
Expand Down Expand Up @@ -45,24 +50,27 @@ describe('createRouter', () => {
beforeAll(async () => {
const mockConfig = new ConfigReader(MockConfig, 'kiali');
const kiali = readKialiConfigs(mockConfig);
const mockCatalog = new CatalogClient({
discoveryApi: HostDiscovery.fromConfig(mockConfig),
});
const logger = createLogger({
transports: [new transports.Console({ silent: true })],
});
const kialiAPI = new KialiApiImpl({ logger, kiali });
app = express();
app.use(express.json());
const router = makeRouter(logger, kialiAPI);
const router = makeRouter(logger, kialiAPI, mockCatalog);
app.use('/', router);
});

beforeEach(() => {
jest.resetAllMocks();
});

describe('GET /config', () => {
describe('POST /config', () => {
it('returns config', async () => {
const response = await request(app)
.get('/config')
.post('/config')
.set('Content-Type', 'application/json');

expect(response.status).toEqual(200);
Expand Down Expand Up @@ -242,41 +250,6 @@ describe('createRouter', () => {
minTLS: 'N/A',
},
username: 'anonymous',
status: {
status: {
'Kiali commit hash': '72a2496cb4ed1545457a68e34fe3e81409b1611d',
'Kiali container version': 'v1.71.0-SNAPSHOT',
'Kiali state': 'running',
'Kiali version': 'v1.71.0-SNAPSHOT',
'Mesh name': 'Istio',
'Mesh version': '1.17.1',
},
externalServices: [
{
name: 'Istio',
version: '1.17.1',
},
{
name: 'Prometheus',
version: '2.34.0',
},
{
name: 'Kubernetes',
version: 'v1.26.3+b404935',
},
{
name: 'Grafana',
},
{
name: 'Jaeger',
},
],
warningMessages: [],
istioEnvironment: {
isMaistra: false,
istioAPIEnabled: true,
},
},
istioCerts: [
{
secretName: 'istio-ca-secret',
Expand Down
85 changes: 60 additions & 25 deletions plugins/kiali-backend/src/service/router.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { errorHandler } from '@backstage/backend-common';
import { CatalogApi } from '@backstage/catalog-client';
import {
CompoundEntityRef,
parseEntityRef,
stringifyEntityRef,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { InputError } from '@backstage/errors';
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node';

import express from 'express';
import express, { Request } from 'express';
import { Logger } from 'winston';

import {
KUBERNETES_ANNOTATION,
KUBERNETES_LABEL_SELECTOR,
KUBERNETES_NAMESPACE,
readKialiConfigs,
} from '@janus-idp/backstage-plugin-kiali-common';
import { readKialiConfigs } from '@janus-idp/backstage-plugin-kiali-common';

import { KialiApiImpl } from '../clients/KialiAPIConnector';

export interface RouterOptions {
logger: Logger;
config: Config;
catalogApi: CatalogApi;
}

export type OverviewQuery = {
Expand All @@ -24,41 +28,71 @@ export type OverviewQuery = {
overviewType?: string;
duration?: number;
direction?: string;
annotation?: { [key: string]: string };
};

export const makeRouter = (
logger: Logger,
kialiAPI: KialiApiImpl,
catalogApi: CatalogApi,
): express.Router => {
const getAnnotations = (query: any): { [key: string]: string } => {
const annotation: { [key: string]: string } = {};
annotation[KUBERNETES_ANNOTATION] =
query[encodeURIComponent(KUBERNETES_ANNOTATION)];
annotation[KUBERNETES_LABEL_SELECTOR] =
query[encodeURIComponent(KUBERNETES_LABEL_SELECTOR)];
annotation[KUBERNETES_NAMESPACE] =
query[encodeURIComponent(KUBERNETES_NAMESPACE)];
return annotation;
const getEntityByReq = async (req: Request<any>) => {
const rawEntityRef = req.body.entityRef;
if (rawEntityRef && typeof rawEntityRef !== 'string') {
throw new InputError(`entity query must be a string`);
} else if (!rawEntityRef) {
throw new InputError('entity is a required field');
}
let entityRef: CompoundEntityRef | undefined = undefined;

try {
entityRef = parseEntityRef(rawEntityRef);
} catch (error) {
throw new InputError(`Invalid entity ref, ${error}`);
}

logger.info(`entityRef: ${JSON.stringify(entityRef)}`);

const token =
getBearerTokenFromAuthorizationHeader(req.headers.authorization) ||
(req.cookies?.token as string | undefined);

logger.info(`Token: ${JSON.stringify(token)}`);
const entity = await catalogApi.getEntityByRef(entityRef, {
token: token,
});

if (!entity) {
throw new InputError(
`Entity ref missing, ${stringifyEntityRef(entityRef)}`,
);
}
return entity;
};

const router = express.Router();
router.use(express.json());

router.get('/config', async (_, res) => {
router.post('/info', async (_, res) => {
logger.debug('Call to Kiali information');
const response = await kialiAPI.fetchInfo();
res.json(response);
});

router.post('/config', async (_, res) => {
logger.debug('Call to Configuration');
const response = await kialiAPI.fetchConfig();
res.json(response);
});

router.get('/overview', async (req, res) => {
router.post('/overview', async (req, res) => {
const entity = await getEntityByReq(req);
const query: OverviewQuery = {
annotation: getAnnotations(req.query),
duration: Number(req.query.duration) || 600,
direction: (req.query.direction as string) || 'inbound',
overviewType: (req.query.overviewType as string) || 'app',
duration: Number(req.body.query.duration) || 600,
direction: (req.body.query.direction as string) || 'inbound',
overviewType: (req.body.query.overviewType as string) || 'app',
};
logger.debug('Call to Overview');
const response = await kialiAPI.fetchOverviewNamespaces(query);
const response = await kialiAPI.fetchOverviewNamespaces(entity, query);
res.json(response);
});

Expand All @@ -72,12 +106,13 @@ export async function createRouter(
): Promise<express.Router> {
const { logger } = options;
const { config } = options;
const { catalogApi } = options;

logger.info('Initializing Kiali backend');

const kiali = readKialiConfigs(config);

const kialiAPI = new KialiApiImpl({ logger, kiali });

return makeRouter(logger, kialiAPI);
return makeRouter(logger, kialiAPI, catalogApi);
}
10 changes: 8 additions & 2 deletions plugins/kiali-backend/src/service/standaloneServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createServiceBuilder } from '@backstage/backend-common';
import { createServiceBuilder, HostDiscovery } from '@backstage/backend-common';
import { CatalogClient } from '@backstage/catalog-client';
import { ConfigReader } from '@backstage/config';

import { Logger } from 'winston';
Expand All @@ -17,10 +18,15 @@ export async function startStandaloneServer(
options: ServerOptions,
): Promise<Server> {
const logger = options.logger.child({ service: 'kiali-backend' });
const config = new ConfigReader({});
const catalogApi = new CatalogClient({
discoveryApi: HostDiscovery.fromConfig(config),
});
logger.debug('Starting application server...');
const router = await createRouter({
logger,
config: new ConfigReader({}),
config,
catalogApi,
});

let service = createServiceBuilder(module)
Expand Down
2 changes: 0 additions & 2 deletions plugins/kiali-common/src/config/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import {
ComputedServerConfig,
defaultServerConfig,
MILLISECONDS,
StatusState,
TLSStatus,
UNIT_TIME,
} from '../types';

export type KialiConfigT = {
meshTLSStatus?: TLSStatus;
server: ComputedServerConfig;
status?: StatusState;
istioCerts?: CertsInfo[];
kialiConsole: string;
username: string;
Expand Down
Loading

0 comments on commit b9a83b3

Please sign in to comment.