mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
clean up / fixes
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
e72e28ddab
commit
5eca8bc01c
@ -18,7 +18,7 @@ export interface ClusterModel {
|
||||
kubeConfigPath: string;
|
||||
|
||||
/** @deprecated */
|
||||
kubeConfig?: string; // kube-config yaml
|
||||
kubeConfig?: string; // yaml
|
||||
}
|
||||
|
||||
export interface ClusterPreferences {
|
||||
@ -48,7 +48,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
|
||||
});
|
||||
}
|
||||
|
||||
@observable activeCluster: ClusterId; // todo: use active "context" from kube-config?
|
||||
@observable activeCluster: ClusterId; // todo: current-context from kube-config?
|
||||
@observable removedClusters = observable.map<ClusterId, Cluster>();
|
||||
@observable clusters = observable.map<ClusterId, Cluster>();
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ export interface IpcMessageHandler {
|
||||
(...args: any[]): any;
|
||||
}
|
||||
|
||||
export function sendMessageToRenderer(channel: IpcChannel, ...args: any[]) {
|
||||
export function sendMessage(channel: IpcChannel, ...args: any[]) {
|
||||
webContents.getFocusedWebContents().send(channel, ...args);
|
||||
}
|
||||
|
||||
|
||||
@ -10,10 +10,10 @@ export const isDevelopment = isDebugging || !isProduction;
|
||||
export const isTestEnv = !!process.env.JEST_WORKER_ID;
|
||||
|
||||
export const appName = `${packageInfo.productName}${isDevelopment ? "Dev" : ""}`
|
||||
export const appProto = "lens" // app's "userData" folder (e.g. "lens://icons/logo.svg")
|
||||
export const staticProto = "static" // static content folder (e.g. "static://RELEASE_NOTES.md")
|
||||
export const appProto = "lens" // app.getPath("userData") folder
|
||||
export const staticProto = "static" // static folder (e.g. "static://RELEASE_NOTES.md")
|
||||
|
||||
// Paths
|
||||
// System paths
|
||||
export const contextDir = process.cwd();
|
||||
export const staticDir = path.join(contextDir, "static");
|
||||
export const outDir = path.join(contextDir, "out");
|
||||
@ -23,12 +23,8 @@ export const htmlTemplate = path.resolve(rendererDir, "template.html");
|
||||
export const sassCommonVars = path.resolve(rendererDir, "components/vars.scss");
|
||||
|
||||
// Apis
|
||||
export const apiPrefix = {
|
||||
BASE: '/api',
|
||||
KUBE_BASE: '/api-kube', // kubernetes cluster api
|
||||
KUBE_HELM: '/api-helm', // helm charts api
|
||||
KUBE_RESOURCE_APPLIER: "/api-resource",
|
||||
};
|
||||
export const apiPrefix = "/api-local" // local router apis
|
||||
export const apiKubePrefix = "/api-kube" // k8s cluster apis
|
||||
|
||||
// Links
|
||||
export const issuesTrackerUrl = "https://github.com/lensapp/lens/issues"
|
||||
|
||||
@ -4,7 +4,7 @@ import path from "path"
|
||||
import http from "http"
|
||||
import { copyFile, ensureDir } from "fs-extra"
|
||||
import filenamify from "filenamify"
|
||||
import { apiPrefix, appProto } from "../common/vars";
|
||||
import { apiKubePrefix, appProto } from "../common/vars";
|
||||
import { ClusterId, ClusterModel, clusterStore } from "../common/cluster-store"
|
||||
import { handleMessages } from "../common/ipc-helpers";
|
||||
import { ClusterIpcMessage } from "../common/ipc-messages";
|
||||
@ -100,7 +100,7 @@ export class ClusterManager {
|
||||
cluster = this.getCluster(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiPrefix.KUBE_BASE)
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -37,8 +37,8 @@ export class Cluster implements ClusterModel {
|
||||
protected kubeconfigManager: KubeconfigManager;
|
||||
|
||||
@observable initialized = false;
|
||||
@observable workspace: string;
|
||||
@observable contextName: string;
|
||||
@observable workspace: string;
|
||||
@observable kubeConfigPath: string;
|
||||
@observable port: number;
|
||||
@observable url: string; // cluster-api url
|
||||
@ -70,14 +70,15 @@ export class Cluster implements ClusterModel {
|
||||
@action
|
||||
async init() {
|
||||
try {
|
||||
// fixme: all broken
|
||||
// fixme
|
||||
this.contextHandler = new ContextHandler(this);
|
||||
this.port = await this.contextHandler.resolveProxyPort(); // resolve port before KubeconfigManager
|
||||
this.webContentUrl = `http://${this.id}.localhost:${this.port}`;
|
||||
this.port = await this.contextHandler.ensurePort(); // resolve port before KubeconfigManager
|
||||
this.kubeAuthProxyUrl = `http://127.0.0.1:${this.port}`;
|
||||
this.kubeconfigManager = new KubeconfigManager(this);
|
||||
|
||||
// this.url = this.kubeconfigManager.getCurrentClusterServer();
|
||||
// this.apiUrl = url.parse(this.url);
|
||||
this.webContentUrl = `http://${this.id}.localhost:${this.port}`;
|
||||
|
||||
logger.info(`[CLUSTER]: init success`, {
|
||||
id: this.id,
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import type { PrometheusProvider, PrometheusService } from "./prometheus/provider-registry"
|
||||
import type { ClusterPreferences } from "../common/cluster-store";
|
||||
import type { ServerOptions } from "http-proxy"
|
||||
import type { Cluster } from "./cluster"
|
||||
import type httpProxy from "http-proxy"
|
||||
import { CoreV1Api } from "@kubernetes/client-node"
|
||||
import { observable } from "mobx";
|
||||
import { prometheusProviders } from "../common/prometheus-providers"
|
||||
import logger from "./logger"
|
||||
import { getFreePort } from "./port"
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy"
|
||||
|
||||
export class ContextHandler {
|
||||
public proxyPort: number
|
||||
@observable proxyPort: number;
|
||||
|
||||
protected proxyServer: KubeAuthProxy
|
||||
protected apiTarget: ServerOptions
|
||||
protected apiTarget: httpProxy.ServerOptions
|
||||
protected prometheusProvider: string
|
||||
protected prometheusPath: string
|
||||
|
||||
@ -44,7 +45,7 @@ export class ContextHandler {
|
||||
}
|
||||
|
||||
public async getPrometheusService(): Promise<PrometheusService> {
|
||||
const providers = this.prometheusProvider ? prometheusProviders.filter((p, _) => p.id == this.prometheusProvider) : prometheusProviders
|
||||
const providers = this.prometheusProvider ? prometheusProviders.filter(provider => provider.id == this.prometheusProvider) : prometheusProviders;
|
||||
const prometheusPromises: Promise<PrometheusService>[] = providers.map(async (provider: PrometheusProvider): Promise<PrometheusService> => {
|
||||
const apiClient = this.cluster.proxyKubeconfig().makeApiClient(CoreV1Api)
|
||||
return await provider.getPrometheusService(apiClient)
|
||||
@ -66,7 +67,7 @@ export class ContextHandler {
|
||||
return this.prometheusPath;
|
||||
}
|
||||
|
||||
public async getApiTarget(isWatchRequest = false): Promise<ServerOptions> {
|
||||
public async getApiTarget(isWatchRequest = false): Promise<httpProxy.ServerOptions> {
|
||||
if (this.apiTarget && !isWatchRequest) {
|
||||
return this.apiTarget
|
||||
}
|
||||
@ -78,24 +79,24 @@ export class ContextHandler {
|
||||
return apiTarget
|
||||
}
|
||||
|
||||
// fixme
|
||||
protected async newApiTarget(timeout: number): Promise<ServerOptions> {
|
||||
public async getApiTargetUrl(): Promise<string> {
|
||||
const port = await this.ensurePort();
|
||||
const { path } = this.cluster.apiUrl;
|
||||
return `http://127.0.0.1:${port}${path}`;
|
||||
}
|
||||
|
||||
protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
|
||||
return {
|
||||
changeOrigin: true,
|
||||
target: await this.getApiTargetUrl(),
|
||||
timeout: timeout,
|
||||
headers: {
|
||||
"Host": this.cluster.apiUrl.hostname
|
||||
},
|
||||
target: {
|
||||
port: await this.resolveProxyPort(),
|
||||
protocol: "http://",
|
||||
host: "localhost",
|
||||
path: this.cluster.apiUrl.path,
|
||||
},
|
||||
"Host": this.cluster.apiUrl.hostname,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async resolveProxyPort(): Promise<number> {
|
||||
async ensurePort(): Promise<number> {
|
||||
if (!this.proxyPort) {
|
||||
this.proxyPort = await getFreePort();
|
||||
}
|
||||
@ -104,7 +105,7 @@ export class ContextHandler {
|
||||
|
||||
public async ensureServer() {
|
||||
if (!this.proxyServer) {
|
||||
await this.resolveProxyPort();
|
||||
await this.ensurePort();
|
||||
const proxyEnv = Object.assign({}, process.env)
|
||||
if (this.cluster.preferences.httpsProxy) {
|
||||
proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { ChildProcess, spawn } from "child_process"
|
||||
import { waitUntilUsed } from "tcp-port-used";
|
||||
import { sendMessageToRenderer } from "../common/ipc-helpers";
|
||||
import { sendMessage } from "../common/ipc-helpers";
|
||||
import type { Cluster } from "./cluster"
|
||||
import { bundledKubectl, Kubectl } from "./kubectl"
|
||||
import logger from "./logger"
|
||||
@ -84,7 +84,7 @@ export class KubeAuthProxy {
|
||||
const channel = `kube-auth:${this.cluster.id}`
|
||||
const message = { data, stream };
|
||||
logger.debug(channel, message);
|
||||
sendMessageToRenderer(channel, message);
|
||||
sendMessage(channel, message);
|
||||
}
|
||||
|
||||
public exit() {
|
||||
|
||||
@ -10,7 +10,11 @@ export class KubeconfigManager {
|
||||
protected tempFile: string
|
||||
|
||||
constructor(protected cluster: Cluster) {
|
||||
this.tempFile = this.createTemporaryKubeconfig();
|
||||
this.init();
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
this.tempFile = await this.createTemporaryKubeconfig();
|
||||
}
|
||||
|
||||
getPath() {
|
||||
@ -21,7 +25,7 @@ export class KubeconfigManager {
|
||||
* Creates new "temporary" kubeconfig that point to the kubectl-proxy.
|
||||
* This way any user of the config does not need to know anything about the auth etc. details.
|
||||
*/
|
||||
protected createTemporaryKubeconfig(): string {
|
||||
protected async createTemporaryKubeconfig(): Promise<string> {
|
||||
fs.ensureDir(this.configDir);
|
||||
const path = `${this.configDir}/${randomFileName("kubeconfig")}`;
|
||||
const { contextName, contextHandler, kubeConfigPath } = this.cluster;
|
||||
@ -29,7 +33,7 @@ export class KubeconfigManager {
|
||||
kubeConfig.clusters = [
|
||||
{
|
||||
name: contextName,
|
||||
server: `http://127.0.0.1:${contextHandler.proxyPort}`, // fixme: extract
|
||||
server: await contextHandler.getApiTargetUrl(),
|
||||
skipTLSVerify: true,
|
||||
}
|
||||
];
|
||||
@ -42,7 +46,7 @@ export class KubeconfigManager {
|
||||
user: "proxy",
|
||||
name: contextName,
|
||||
cluster: contextName,
|
||||
// namespace: kubeConfig.getContextObject(contextName).namespace,
|
||||
namespace: kubeConfig.getContextObject(contextName).namespace,
|
||||
}
|
||||
];
|
||||
logger.info(`Creating temp config for context "${contextName}" at "${path}"`);
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
import net from "net";
|
||||
import http from "http";
|
||||
import httpProxy from "http-proxy";
|
||||
import { Socket } from "net";
|
||||
import * as url from "url";
|
||||
import url from "url";
|
||||
import * as WebSocket from "ws"
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import * as nodeShell from "./node-shell-session"
|
||||
import { ClusterManager } from "./cluster-manager"
|
||||
import { Router } from "./router"
|
||||
import { apiPrefix } from "../common/vars";
|
||||
import { ClusterManager } from "./cluster-manager"
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
import logger from "./logger"
|
||||
|
||||
export class LensProxy {
|
||||
@ -27,7 +27,7 @@ export class LensProxy {
|
||||
}
|
||||
|
||||
listen(): this {
|
||||
const proxyServer = this.buildProxyServer();
|
||||
const proxyServer = this.buildCustomProxy();
|
||||
const { proxyPort } = this.clusterManager;
|
||||
this.proxyServer = proxyServer.listen(proxyPort);
|
||||
logger.info(`LensProxy server has started http://localhost:${proxyPort}`);
|
||||
@ -40,21 +40,21 @@ export class LensProxy {
|
||||
this.closed = true
|
||||
}
|
||||
|
||||
protected buildProxyServer() {
|
||||
protected buildCustomProxy(): http.Server {
|
||||
const proxy = this.createProxy();
|
||||
const proxyServer = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
const customProxy = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
this.handleRequest(proxy, req, res);
|
||||
});
|
||||
proxyServer.on("upgrade", (req: http.IncomingMessage, socket: Socket, head: Buffer) => {
|
||||
customProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
||||
this.handleWsUpgrade(req, socket, head)
|
||||
});
|
||||
proxyServer.on("error", (err) => {
|
||||
customProxy.on("error", (err) => {
|
||||
logger.error("proxy error", err)
|
||||
});
|
||||
return proxyServer;
|
||||
return customProxy;
|
||||
}
|
||||
|
||||
protected createProxy() {
|
||||
protected createProxy(): httpProxy {
|
||||
const proxy = httpProxy.createProxyServer();
|
||||
|
||||
proxy.on("proxyRes", (proxyRes, req, res) => {
|
||||
@ -71,16 +71,18 @@ export class LensProxy {
|
||||
if (req.method !== "GET") {
|
||||
return
|
||||
}
|
||||
const key = `${req.headers.host}${req.url}`
|
||||
if (this.retryCounters.has(key)) {
|
||||
logger.debug("Resetting proxy retry cache for url: " + key)
|
||||
this.retryCounters.delete(key)
|
||||
const reqUrl = `${req.headers.host}${req.url}`
|
||||
if (this.retryCounters.has(reqUrl)) {
|
||||
logger.debug("Resetting proxy retry cache for url: " + reqUrl)
|
||||
this.retryCounters.delete(reqUrl)
|
||||
}
|
||||
})
|
||||
proxy.on("error", (error, req, res, target) => {
|
||||
if (this.closed) return;
|
||||
if (this.closed) {
|
||||
return;
|
||||
}
|
||||
if (target) {
|
||||
logger.debug("Failed proxy to target: " + JSON.stringify(target))
|
||||
logger.debug("Failed proxy to target: " + JSON.stringify(target, null, 2));
|
||||
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
|
||||
const retryCounterKey = `${req.headers.host}${req.url}`
|
||||
const retryCount = this.retryCounters.get(retryCounterKey) || 0
|
||||
@ -112,11 +114,11 @@ export class LensProxy {
|
||||
}));
|
||||
}
|
||||
|
||||
// fixme: remove api prefix?
|
||||
protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise<httpProxy.ServerOptions> {
|
||||
const prefix = apiPrefix.KUBE_BASE;
|
||||
if (req.url.startsWith(prefix)) {
|
||||
if (req.url.startsWith(apiKubePrefix)) {
|
||||
delete req.headers.authorization
|
||||
req.url = req.url.replace(prefix, "")
|
||||
req.url = req.url.replace(apiKubePrefix, "")
|
||||
const isWatchRequest = req.url.includes("watch=")
|
||||
return await contextHandler.getApiTarget(isWatchRequest)
|
||||
}
|
||||
@ -137,11 +139,11 @@ export class LensProxy {
|
||||
if (proxyTarget) {
|
||||
proxy.web(req, res, proxyTarget)
|
||||
} else {
|
||||
this.router.route(cluster, req, res); // todo: handle "not-found" if isBoom==true?
|
||||
this.router.route(cluster, req, res);
|
||||
}
|
||||
}
|
||||
|
||||
protected async handleWsUpgrade(req: http.IncomingMessage, socket: Socket, head: Buffer) {
|
||||
protected async handleWsUpgrade(req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
||||
const wsServer = this.createWsListener();
|
||||
wsServer.handleUpgrade(req, socket, head, (con) => {
|
||||
wsServer.emit("connection", con, req);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import logger from "./logger"
|
||||
import { createServer, AddressInfo } from "net"
|
||||
|
||||
// todo: replace with https://github.com/http-party/node-portfinder ?
|
||||
// todo: use https://github.com/http-party/node-portfinder ?
|
||||
|
||||
const getNextAvailablePort = () => {
|
||||
logger.debug("getNextAvailablePort() start")
|
||||
|
||||
@ -9,37 +9,28 @@ import { resourceApplierApi } from "./resource-applier-api"
|
||||
import { apiPrefix, appName, outDir } from "../common/vars";
|
||||
import { configRoute, kubeconfigRoute, metricsRoute, portForwardRoute, watchRoute } from "./routes";
|
||||
|
||||
const mimeTypes: Record<string, string> = {
|
||||
"html": "text/html",
|
||||
"txt": "text/plain",
|
||||
"css": "text/css",
|
||||
"gif": "image/gif",
|
||||
"jpg": "image/jpeg",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
"js": "application/javascript",
|
||||
"woff2": "font/woff2",
|
||||
"ttf": "font/ttf"
|
||||
};
|
||||
|
||||
interface RouteParams {
|
||||
[key: string]: string | undefined;
|
||||
export interface RouterRequestOpts<P extends Record<string, string> = any> {
|
||||
req: http.IncomingMessage;
|
||||
res: http.ServerResponse;
|
||||
cluster: Cluster;
|
||||
url: URL;
|
||||
params: P; // https://hapi.dev/module/call/api
|
||||
}
|
||||
|
||||
export type LensApiRequest = {
|
||||
export interface LensApiRequest<D = any, P = any> {
|
||||
path: string;
|
||||
payload: D;
|
||||
params: P;
|
||||
cluster: Cluster;
|
||||
payload: any;
|
||||
raw: {
|
||||
req: http.IncomingMessage;
|
||||
};
|
||||
params: RouteParams;
|
||||
response: http.ServerResponse;
|
||||
query: URLSearchParams;
|
||||
path: string;
|
||||
raw: {
|
||||
req: http.IncomingMessage;
|
||||
}
|
||||
}
|
||||
|
||||
export class Router {
|
||||
protected router: any
|
||||
protected router: any;
|
||||
|
||||
public constructor() {
|
||||
this.router = new Call.Router();
|
||||
@ -47,20 +38,23 @@ export class Router {
|
||||
}
|
||||
|
||||
public async route(cluster: Cluster, req: http.IncomingMessage, res: http.ServerResponse): Promise<boolean> {
|
||||
const reqUrl = new URL(req.url, "http://localhost");
|
||||
const path = reqUrl.pathname
|
||||
const url = new URL(req.url, "http://localhost");
|
||||
const path = url.pathname
|
||||
const method = req.method.toLowerCase()
|
||||
const matchingRoute = this.router.route(method, path);
|
||||
const routeExists = !matchingRoute.isBoom;
|
||||
if (routeExists) {
|
||||
const request = await this.getRequest({ req, res, cluster, url: reqUrl, params: matchingRoute.params })
|
||||
const routeFound = !matchingRoute.isBoom;
|
||||
if (routeFound) {
|
||||
const request = await this.getRequest({
|
||||
req, res, cluster, url,
|
||||
params: matchingRoute.params
|
||||
});
|
||||
await matchingRoute.route(request)
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected async getRequest(opts: { req: http.IncomingMessage; res: http.ServerResponse; cluster: Cluster; url: URL; params: RouteParams }) {
|
||||
protected async getRequest(opts: RouterRequestOpts) {
|
||||
const { req, res, url, cluster, params } = opts
|
||||
const { payload } = await Subtext.parse(req, null, { parse: true, output: 'data' });
|
||||
const request: LensApiRequest = {
|
||||
@ -78,6 +72,18 @@ export class Router {
|
||||
}
|
||||
|
||||
protected getMimeType(filename: string) {
|
||||
const mimeTypes: Record<string, string> = {
|
||||
html: "text/html",
|
||||
txt: "text/plain",
|
||||
css: "text/css",
|
||||
gif: "image/gif",
|
||||
jpg: "image/jpeg",
|
||||
png: "image/png",
|
||||
svg: "image/svg+xml",
|
||||
js: "application/javascript",
|
||||
woff2: "font/woff2",
|
||||
ttf: "font/ttf"
|
||||
};
|
||||
return mimeTypes[path.extname(filename).slice(1)] || "text/plain"
|
||||
}
|
||||
|
||||
@ -94,12 +100,6 @@ export class Router {
|
||||
}
|
||||
|
||||
protected addRoutes() {
|
||||
const {
|
||||
BASE: apiBase,
|
||||
KUBE_HELM: apiHelm,
|
||||
KUBE_RESOURCE_APPLIER: apiResource,
|
||||
} = apiPrefix;
|
||||
|
||||
// Static assets
|
||||
this.router.add({ method: 'get', path: '/{path*}' }, (request: LensApiRequest) => {
|
||||
const { response, params } = request
|
||||
@ -107,33 +107,33 @@ export class Router {
|
||||
this.handleStaticFile(file, response)
|
||||
})
|
||||
|
||||
this.router.add({ method: "get", path: `${apiBase}/config` }, configRoute.routeConfig.bind(configRoute))
|
||||
this.router.add({ method: "get", path: `${apiBase}/kubeconfig/service-account/{namespace}/{account}` }, kubeconfigRoute.routeServiceAccountRoute.bind(kubeconfigRoute))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/config` }, configRoute.routeConfig.bind(configRoute))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/kubeconfig/service-account/{namespace}/{account}` }, kubeconfigRoute.routeServiceAccountRoute.bind(kubeconfigRoute))
|
||||
|
||||
// Watch API
|
||||
this.router.add({ method: "get", path: `${apiBase}/watch` }, watchRoute.routeWatch.bind(watchRoute))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/watch` }, watchRoute.routeWatch.bind(watchRoute))
|
||||
|
||||
// Metrics API
|
||||
this.router.add({ method: "post", path: `${apiBase}/metrics` }, metricsRoute.routeMetrics.bind(metricsRoute))
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, metricsRoute.routeMetrics.bind(metricsRoute))
|
||||
|
||||
// Port-forward API
|
||||
this.router.add({ method: "post", path: `${apiBase}/services/{namespace}/{service}/port-forward/{port}` }, portForwardRoute.routeServicePortForward.bind(portForwardRoute))
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/services/{namespace}/{service}/port-forward/{port}` }, portForwardRoute.routeServicePortForward.bind(portForwardRoute))
|
||||
|
||||
// Helm API
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/charts` }, helmApi.listCharts.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/charts/{repo}/{chart}` }, helmApi.getChart.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/charts/{repo}/{chart}/values` }, helmApi.getChartValues.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, helmApi.listCharts.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}` }, helmApi.getChart.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts/{repo}/{chart}/values` }, helmApi.getChartValues.bind(helmApi))
|
||||
|
||||
this.router.add({ method: "post", path: `${apiHelm}/v2/releases` }, helmApi.installChart.bind(helmApi))
|
||||
this.router.add({ method: `put`, path: `${apiHelm}/v2/releases/{namespace}/{release}` }, helmApi.updateRelease.bind(helmApi))
|
||||
this.router.add({ method: `put`, path: `${apiHelm}/v2/releases/{namespace}/{release}/rollback` }, helmApi.rollbackRelease.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/releases/{namespace?}` }, helmApi.listReleases.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/releases/{namespace}/{release}` }, helmApi.getRelease.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/releases/{namespace}/{release}/values` }, helmApi.getReleaseValues.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiHelm}/v2/releases/{namespace}/{release}/history` }, helmApi.getReleaseHistory.bind(helmApi))
|
||||
this.router.add({ method: "delete", path: `${apiHelm}/v2/releases/{namespace}/{release}` }, helmApi.deleteRelease.bind(helmApi))
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/v2/releases` }, helmApi.installChart.bind(helmApi))
|
||||
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.updateRelease.bind(helmApi))
|
||||
this.router.add({ method: `put`, path: `${apiPrefix}/v2/releases/{namespace}/{release}/rollback` }, helmApi.rollbackRelease.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace?}` }, helmApi.listReleases.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.getRelease.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/values` }, helmApi.getReleaseValues.bind(helmApi))
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/releases/{namespace}/{release}/history` }, helmApi.getReleaseHistory.bind(helmApi))
|
||||
this.router.add({ method: "delete", path: `${apiPrefix}/v2/releases/{namespace}/{release}` }, helmApi.deleteRelease.bind(helmApi))
|
||||
|
||||
// Resource Applier API
|
||||
this.router.add({ method: "post", path: `${apiResource}/stack` }, resourceApplierApi.applyResource.bind(resourceApplierApi))
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, resourceApplierApi.applyResource.bind(resourceApplierApi))
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,55 +2,41 @@ import { LensApiRequest } from "../router"
|
||||
import { LensApi } from "../lens-api"
|
||||
import requestPromise from "request-promise-native"
|
||||
import { PrometheusClusterQuery, PrometheusIngressQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusProvider, PrometheusPvcQuery, PrometheusQueryOpts } from "../prometheus/provider-registry"
|
||||
import { apiPrefix } from "../../common/vars";
|
||||
|
||||
export type IMetricsQuery = string | string[] | {
|
||||
[metricName: string]: string;
|
||||
}
|
||||
|
||||
class MetricsRoute extends LensApi {
|
||||
|
||||
public async routeMetrics(request: LensApiRequest) {
|
||||
const { response, cluster } = request
|
||||
const serverUrl = `http://127.0.0.1:${cluster.port}${apiPrefix.KUBE_BASE}` // fixme: extract
|
||||
const query: IMetricsQuery = request.payload;
|
||||
const headers: Record<string, string> = {
|
||||
"Host": `${cluster.id}.localhost:${cluster.port}`,
|
||||
"Content-type": "application/json",
|
||||
}
|
||||
const queryParams: IMetricsQuery = {}
|
||||
request.query.forEach((value: string, key: string) => {
|
||||
queryParams[key] = value
|
||||
})
|
||||
|
||||
public async routeMetrics(request: LensApiRequest<IMetricsQuery>) {
|
||||
const { response, cluster, payload } = request
|
||||
const { contextHandler } = cluster;
|
||||
const serverUrl = await contextHandler.getApiTargetUrl();
|
||||
let metricsUrl: string
|
||||
let prometheusProvider: PrometheusProvider
|
||||
try {
|
||||
const prometheusPath = await cluster.contextHandler.getPrometheusPath()
|
||||
const prometheusPath = await contextHandler.getPrometheusPath()
|
||||
metricsUrl = `${serverUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range`
|
||||
prometheusProvider = await cluster.contextHandler.getPrometheusProvider()
|
||||
prometheusProvider = await contextHandler.getPrometheusProvider()
|
||||
} catch {
|
||||
this.respondJson(response, {})
|
||||
return
|
||||
}
|
||||
// prometheus metrics loader
|
||||
const attempts: { [query: string]: number } = {};
|
||||
const attempts: Record<string, number> = {};
|
||||
const maxAttempts = 5;
|
||||
const loadMetrics = (orgQuery: string): Promise<any> => {
|
||||
const query = orgQuery.trim()
|
||||
const attempt = attempts[query] = (attempts[query] || 0) + 1;
|
||||
const loadMetrics = (promQuery: string): Promise<any> => {
|
||||
const queryString = request.query.toString() + `&query=` + promQuery;
|
||||
const attempt = attempts[queryString] = (attempts[queryString] || 0) + 1;
|
||||
return requestPromise(metricsUrl, {
|
||||
resolveWithFullResponse: false,
|
||||
headers: headers,
|
||||
json: true,
|
||||
qs: {
|
||||
query: query,
|
||||
...queryParams
|
||||
}
|
||||
qs: queryString,
|
||||
useQuerystring: true,
|
||||
resolveWithFullResponse: false,
|
||||
}).catch(async (error) => {
|
||||
if (attempt < maxAttempts && (error.statusCode && error.statusCode != 404)) {
|
||||
await new Promise(resolve => setTimeout(resolve, attempt * 1000)); // add delay before repeating request
|
||||
return loadMetrics(query);
|
||||
return loadMetrics(queryString);
|
||||
}
|
||||
return {
|
||||
status: error.toString(),
|
||||
@ -63,14 +49,14 @@ class MetricsRoute extends LensApi {
|
||||
|
||||
// return data in same structure as query
|
||||
let data: any;
|
||||
if (typeof query === "string") {
|
||||
data = await loadMetrics(query)
|
||||
} else if (Array.isArray(query)) {
|
||||
data = await Promise.all(query.map(loadMetrics));
|
||||
if (typeof payload === "string") {
|
||||
data = await loadMetrics(payload)
|
||||
} else if (Array.isArray(payload)) {
|
||||
data = await Promise.all(payload.map(loadMetrics));
|
||||
} else {
|
||||
data = {};
|
||||
const result = await Promise.all(
|
||||
Object.entries(query).map((queryEntry: any) => {
|
||||
Object.entries(payload).map((queryEntry: any) => {
|
||||
const queryName: string = queryEntry[0]
|
||||
const queryOpts: PrometheusQueryOpts = queryEntry[1]
|
||||
const queries = prometheusProvider.getQueries(queryOpts)
|
||||
@ -78,7 +64,7 @@ class MetricsRoute extends LensApi {
|
||||
return loadMetrics(q)
|
||||
})
|
||||
);
|
||||
Object.keys(query).forEach((metricName, index) => {
|
||||
Object.keys(payload).forEach((metricName, index) => {
|
||||
data[metricName] = result[index];
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { compile } from "path-to-regexp";
|
||||
import { apiHelm } from "../index";
|
||||
import { apiBase } from "../index";
|
||||
import { stringify } from "querystring";
|
||||
import { autobind } from "../../utils";
|
||||
|
||||
@ -21,7 +21,7 @@ const endpoint = compile(`/v2/charts/:repo?/:name?`) as (params?: {
|
||||
|
||||
export const helmChartsApi = {
|
||||
list() {
|
||||
return apiHelm
|
||||
return apiBase
|
||||
.get<IHelmChartList>(endpoint())
|
||||
.then(data => {
|
||||
return Object
|
||||
@ -33,7 +33,7 @@ export const helmChartsApi = {
|
||||
|
||||
get(repo: string, name: string, readmeVersion?: string) {
|
||||
const path = endpoint({ repo, name });
|
||||
return apiHelm
|
||||
return apiBase
|
||||
.get<IHelmChartDetails>(path + "?" + stringify({ version: readmeVersion }))
|
||||
.then(data => {
|
||||
const versions = data.versions.map(HelmChart.create);
|
||||
@ -46,7 +46,7 @@ export const helmChartsApi = {
|
||||
},
|
||||
|
||||
getValues(repo: string, name: string, version: string) {
|
||||
return apiHelm
|
||||
return apiBase
|
||||
.get<string>(`/v2/charts/${repo}/${name}/values?` + stringify({ version }));
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import jsYaml from "js-yaml";
|
||||
import { compile } from "path-to-regexp";
|
||||
import { autobind, formatDuration } from "../../utils";
|
||||
import capitalize from "lodash/capitalize";
|
||||
import { apiHelm } from "../index";
|
||||
import { apiBase } from "../index";
|
||||
import { helmChartStore } from "../../components/+apps-helm-charts/helm-chart.store";
|
||||
import { ItemObject } from "../../item.store";
|
||||
import { KubeObject } from "../kube-object";
|
||||
@ -69,14 +69,14 @@ const endpoint = compile(`/v2/releases/:namespace?/:name?`) as (
|
||||
|
||||
export const helmReleasesApi = {
|
||||
list(namespace?: string) {
|
||||
return apiHelm
|
||||
return apiBase
|
||||
.get<HelmRelease[]>(endpoint({ namespace }))
|
||||
.then(releases => releases.map(HelmRelease.create));
|
||||
},
|
||||
|
||||
get(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace });
|
||||
return apiHelm.get<IReleaseRawDetails>(path).then(details => {
|
||||
return apiBase.get<IReleaseRawDetails>(path).then(details => {
|
||||
const items: KubeObject[] = JSON.parse(details.resources).items;
|
||||
const resources = items.map(item => KubeObject.create(item));
|
||||
return {
|
||||
@ -90,34 +90,34 @@ export const helmReleasesApi = {
|
||||
const { repo, ...data } = payload;
|
||||
data.chart = `${repo}/${data.chart}`;
|
||||
data.values = jsYaml.safeLoad(data.values);
|
||||
return apiHelm.post(endpoint(), { data });
|
||||
return apiBase.post(endpoint(), { data });
|
||||
},
|
||||
|
||||
update(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise<IReleaseUpdateDetails> {
|
||||
const { repo, ...data } = payload;
|
||||
data.chart = `${repo}/${data.chart}`;
|
||||
data.values = jsYaml.safeLoad(data.values);
|
||||
return apiHelm.put(endpoint({ name, namespace }), { data });
|
||||
return apiBase.put(endpoint({ name, namespace }), { data });
|
||||
},
|
||||
|
||||
async delete(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace });
|
||||
return apiHelm.del(path);
|
||||
return apiBase.del(path);
|
||||
},
|
||||
|
||||
getValues(name: string, namespace: string) {
|
||||
const path = endpoint({ name, namespace }) + "/values";
|
||||
return apiHelm.get<string>(path);
|
||||
return apiBase.get<string>(path);
|
||||
},
|
||||
|
||||
getHistory(name: string, namespace: string): Promise<IReleaseRevision[]> {
|
||||
const path = endpoint({ name, namespace }) + "/history";
|
||||
return apiHelm.get(path);
|
||||
return apiBase.get(path);
|
||||
},
|
||||
|
||||
rollback(name: string, namespace: string, revision: number) {
|
||||
const path = endpoint({ name, namespace }) + "/rollback";
|
||||
return apiHelm.put(path, {
|
||||
return apiBase.put(path, {
|
||||
data: {
|
||||
revision: revision
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import jsYaml from "js-yaml"
|
||||
import { KubeObject } from "../kube-object";
|
||||
import { KubeJsonApiData } from "../kube-json-api";
|
||||
import { apiResourceApplier } from "../index";
|
||||
import { apiBase } from "../index";
|
||||
import { apiManager } from "../api-manager";
|
||||
|
||||
export const resourceApplierApi = {
|
||||
@ -13,7 +13,7 @@ export const resourceApplierApi = {
|
||||
if (typeof resource === "string") {
|
||||
resource = jsYaml.safeLoad(resource);
|
||||
}
|
||||
return apiResourceApplier
|
||||
return apiBase
|
||||
.post<KubeJsonApiData[]>("/stack", { data: resource })
|
||||
.then(data => {
|
||||
const items = data.map(obj => {
|
||||
|
||||
@ -1,25 +1,15 @@
|
||||
import { JsonApi, JsonApiErrorParsed } from "./json-api";
|
||||
import { KubeJsonApi } from "./kube-json-api";
|
||||
import { Notifications } from "../components/notifications";
|
||||
import { apiPrefix, isDevelopment } from "../../common/vars";
|
||||
|
||||
//-- JSON HTTP APIS
|
||||
import { apiKubePrefix, apiPrefix, isDevelopment } from "../../common/vars";
|
||||
|
||||
export const apiBase = new JsonApi({
|
||||
debug: isDevelopment,
|
||||
apiPrefix: apiPrefix.BASE,
|
||||
apiPrefix: apiPrefix,
|
||||
});
|
||||
export const apiKube = new KubeJsonApi({
|
||||
debug: isDevelopment,
|
||||
apiPrefix: apiPrefix.KUBE_BASE,
|
||||
});
|
||||
export const apiHelm = new KubeJsonApi({
|
||||
debug: isDevelopment,
|
||||
apiPrefix: apiPrefix.KUBE_HELM,
|
||||
});
|
||||
export const apiResourceApplier = new KubeJsonApi({
|
||||
debug: isDevelopment,
|
||||
apiPrefix: apiPrefix.KUBE_RESOURCE_APPLIER,
|
||||
apiPrefix: apiKubePrefix,
|
||||
});
|
||||
|
||||
// Common handler for HTTP api errors
|
||||
@ -34,5 +24,3 @@ function onApiError(error: JsonApiErrorParsed, res: Response) {
|
||||
|
||||
apiBase.onError.addListener(onApiError);
|
||||
apiKube.onError.addListener(onApiError);
|
||||
apiHelm.onError.addListener(onApiError);
|
||||
apiResourceApplier.onError.addListener(onApiError);
|
||||
|
||||
@ -3,12 +3,19 @@
|
||||
import merge from "lodash/merge"
|
||||
import { stringify } from "querystring";
|
||||
import { IKubeObjectConstructor, KubeObject } from "./kube-object";
|
||||
import { IKubeObjectRef, KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
|
||||
import { KubeJsonApi, KubeJsonApiData, KubeJsonApiDataList } from "./kube-json-api";
|
||||
import { apiKube } from "./index";
|
||||
import { kubeWatchApi } from "./kube-watch-api";
|
||||
import { apiManager } from "./api-manager";
|
||||
import { createApiLink, parseApi } from "./kube-api-parse";
|
||||
|
||||
export interface IKubeObjectRef {
|
||||
kind: string;
|
||||
apiVersion: string;
|
||||
name: string;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface IKubeApiOptions<T extends KubeObject> {
|
||||
kind: string; // resource type within api-group, e.g. "Namespace"
|
||||
apiBase: string; // base api-path for listing all resources, e.g. "/api/v1/pods"
|
||||
|
||||
@ -31,13 +31,6 @@ export interface KubeJsonApiData extends JsonApiData {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IKubeObjectRef {
|
||||
kind: string;
|
||||
apiVersion: string;
|
||||
name: string;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
export interface KubeJsonApiError extends JsonApiError {
|
||||
code: number;
|
||||
status: string;
|
||||
@ -49,14 +42,6 @@ export interface KubeJsonApiError extends JsonApiError {
|
||||
};
|
||||
}
|
||||
|
||||
export interface IKubeJsonApiQuery {
|
||||
watch?: any;
|
||||
resourceVersion?: string;
|
||||
timeoutSeconds?: number;
|
||||
limit?: number; // doesn't work with ?watch
|
||||
continue?: string; // might be used with ?limit from second request
|
||||
}
|
||||
|
||||
export class KubeJsonApi extends JsonApi<KubeJsonApiData> {
|
||||
protected parseError(error: KubeJsonApiError | any, res: Response): string[] {
|
||||
const { status, reason, message } = error;
|
||||
|
||||
@ -29,7 +29,6 @@ export interface IKubeWatchRouteQuery {
|
||||
export class KubeWatchApi {
|
||||
protected evtSource: EventSource;
|
||||
protected onData = new EventEmitter<[IKubeWatchEvent]>();
|
||||
protected apiUrl = apiPrefix.BASE + "/watch";
|
||||
protected subscribers = observable.map<KubeApi, number>();
|
||||
protected reconnectTimeoutMs = 5000;
|
||||
protected maxReconnectsOnError = 10;
|
||||
@ -79,7 +78,7 @@ export class KubeWatchApi {
|
||||
return;
|
||||
}
|
||||
const query = this.getQuery();
|
||||
const apiUrl = this.apiUrl + "?" + stringify(query);
|
||||
const apiUrl = `${apiPrefix}/watch?` + stringify(query);
|
||||
this.evtSource = new EventSource(apiUrl);
|
||||
this.evtSource.onmessage = this.onMessage;
|
||||
this.evtSource.onerror = this.onError;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user