Make routes in back-end comply to Open Closed Principle (#4859)

* Make registration of back-end routes happen using injectable

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for list charts

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for get chart

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for get chart values

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for installing chart

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for updating release

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for rollbacking release

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for listing releases

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting release

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting release values

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting release history

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for deleting release

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove HelmRoute for not being used anymore

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for applying resource

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for patching resource

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for stopping current port forward

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting current port forward

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Consolidate start port forward route to use injection token for registering route

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for metrics

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting service account

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for getting version

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add global override for reading file to make it not required where not interesting

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Switch to using injectable to setup route for serving static files

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make adding routes private for router

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make routes wait until all asynchronous stuff are done

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Introduce healthy abstraction between back-end routes and route handlers

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make response of route typed

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix incorrect return value of updateRelease

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make typing of routes support synchronous values

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make code cleaner

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Rename test for accuracy

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Add test for throwing route handler

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove duplicate license header

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Make mechanism of creating HTTP response an implementation detail

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Remove not needed properties from test

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix code style

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix typing and codestyle

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix typing error

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Fix merge conflicts

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>

* Organize all router related files under directory

Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
Janne Savolainen authored and GitHub committed 2022-03-11 12:20:00 +02:00
1 parent 772e879b81
commit 38af26efc9
47 files changed
+1646 -810

No files matched your search

@@ -0,0 +1,108 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import { apiPrefix } from "../../../common/vars";
import type { LensApiRequest, Route } from "../../router/router";
import { routeInjectionToken } from "../../router/router.injectable";
import { ClusterMetadataKey, ClusterPrometheusMetadata } from "../../../common/cluster-types";
import logger from "../../logger";
import type { Cluster } from "../../../common/cluster/cluster";
import { getMetrics } from "../../k8s-request";
import type { IMetricsQuery } from "./metrics-query";
// This is used for backoff retry tracking.
const ATTEMPTS = [false, false, false, false, true];
async function loadMetrics(promQueries: string[], cluster: Cluster, prometheusPath: string, queryParams: Record<string, string>): Promise<any[]> {
const queries = promQueries.map(p => p.trim());
const loaders = new Map<string, Promise<any>>();
async function loadMetric(query: string): Promise<any> {
async function loadMetricHelper(): Promise<any> {
for (const [attempt, lastAttempt] of ATTEMPTS.entries()) { // retry
try {
return await getMetrics(cluster, prometheusPath, { query, ...queryParams });
} catch (error) {
if (lastAttempt || (error?.statusCode >= 400 && error?.statusCode < 500)) {
logger.error("[Metrics]: metrics not available", error?.response ? error.response?.body : error);
throw new Error("Metrics not available");
}
await new Promise(resolve => setTimeout(resolve, (attempt + 1) * 1000)); // add delay before repeating request
}
}
}
return loaders.get(query) ?? loaders.set(query, loadMetricHelper()).get(query);
}
return Promise.all(queries.map(loadMetric));
}
const addMetricsRoute = async ({ cluster, payload, query }: LensApiRequest) => {
const queryParams: IMetricsQuery = Object.fromEntries(query.entries());
const prometheusMetadata: ClusterPrometheusMetadata = {};
try {
const { prometheusPath, provider } = await cluster.contextHandler.getPrometheusDetails();
prometheusMetadata.provider = provider?.id;
prometheusMetadata.autoDetected = !cluster.preferences.prometheusProvider?.type;
if (!prometheusPath) {
prometheusMetadata.success = false;
return { response: {}};
}
// return data in same structure as query
if (typeof payload === "string") {
const [data] = await loadMetrics([payload], cluster, prometheusPath, queryParams);
return { response: data };
}
if (Array.isArray(payload)) {
const data = await loadMetrics(payload, cluster, prometheusPath, queryParams);
return { response: data };
}
const queries = Object.entries<Record<string, string>>(payload)
.map(([queryName, queryOpts]) => (
provider.getQuery(queryOpts, queryName)
));
const result = await loadMetrics(queries, cluster, prometheusPath, queryParams);
const data = Object.fromEntries(Object.keys(payload).map((metricName, i) => [metricName, result[i]]));
prometheusMetadata.success = true;
return { response: data };
} catch (error) {
prometheusMetadata.success = false;
logger.warn(`[METRICS-ROUTE]: failed to get metrics for clusterId=${cluster.id}:`, error);
return { response: {}};
} finally {
cluster.metadata[ClusterMetadataKey.PROMETHEUS] = prometheusMetadata;
}
};
const addMetricsRouteInjectable = getInjectable({
id: "add-metrics-route",
instantiate: (): Route<any> => ({
method: "post",
path: `${apiPrefix}/metrics`,
handler: addMetricsRoute,
}),
injectionToken: routeInjectionToken,
});
export default addMetricsRouteInjectable;