mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into extensions-api
# Conflicts: # src/renderer/components/app.tsx # src/renderer/components/cluster-manager/clusters-menu.scss # src/renderer/components/cluster-manager/clusters-menu.tsx # src/renderer/components/layout/main-layout.tsx # src/renderer/components/layout/sidebar.tsx
This commit is contained in:
commit
91b4823ec6
54 files changed
+619
-333
No files matched your search
@@ -44,7 +44,7 @@ export class ClusterManager {
|
||||
// lens-server is connecting to 127.0.0.1:<port>/<uid>
|
||||
if (req.headers.host.startsWith("127.0.0.1")) {
|
||||
const clusterId = req.url.split("/")[1]
|
||||
const cluster = clusterStore.getById(clusterId)
|
||||
cluster = clusterStore.getById(clusterId)
|
||||
if (cluster) {
|
||||
// we need to swap path prefix so that request is proxied to kube api
|
||||
req.url = req.url.replace(`/${clusterId}`, apiKubePrefix)
|
||||
|
||||
+12
-9
@@ -92,7 +92,7 @@ export class Cluster implements ClusterModel {
|
||||
async init(port: number) {
|
||||
try {
|
||||
this.contextHandler = new ContextHandler(this);
|
||||
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler);
|
||||
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port);
|
||||
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
|
||||
this.initialized = true;
|
||||
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
|
||||
@@ -111,12 +111,10 @@ export class Cluster implements ClusterModel {
|
||||
protected bindEvents() {
|
||||
logger.info(`[CLUSTER]: bind events`, this.getMeta());
|
||||
const refreshTimer = setInterval(() => this.online && this.refresh(), 30000); // every 30s
|
||||
const refreshEventsTimer = setInterval(() => this.online && this.refreshEvents(), 3000); // every 3s
|
||||
|
||||
this.eventDisposers.push(
|
||||
reaction(this.getState, this.pushState),
|
||||
() => clearInterval(refreshTimer),
|
||||
() => clearInterval(refreshEventsTimer),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,7 +133,13 @@ export class Cluster implements ClusterModel {
|
||||
if (this.disconnected || (!init && !this.accessible)) {
|
||||
await this.reconnect();
|
||||
}
|
||||
await this.refresh();
|
||||
await this.refreshConnectionStatus()
|
||||
if (this.accessible) {
|
||||
await this.refreshAllowedResources()
|
||||
this.ready = true
|
||||
this.kubeCtl = new Kubectl(this.version)
|
||||
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
||||
}
|
||||
return this.pushState();
|
||||
}
|
||||
|
||||
@@ -161,15 +165,14 @@ export class Cluster implements ClusterModel {
|
||||
@action
|
||||
async refresh() {
|
||||
logger.info(`[CLUSTER]: refresh`, this.getMeta());
|
||||
await this.refreshConnectionStatus(); // refresh "version", "online", etc.
|
||||
await this.whenInitialized;
|
||||
await this.refreshConnectionStatus();
|
||||
if (this.accessible) {
|
||||
this.kubeCtl = new Kubectl(this.version)
|
||||
this.distribution = this.detectKubernetesDistribution(this.version)
|
||||
const [features, isAdmin, nodesCount] = await Promise.all([
|
||||
getFeatures(this),
|
||||
this.isClusterAdmin(),
|
||||
this.getNodeCount(),
|
||||
this.kubeCtl.ensureKubectl()
|
||||
]);
|
||||
this.features = features;
|
||||
this.isAdmin = isAdmin;
|
||||
@@ -178,8 +181,8 @@ export class Cluster implements ClusterModel {
|
||||
this.refreshEvents(),
|
||||
this.refreshAllowedResources(),
|
||||
]);
|
||||
this.ready = true
|
||||
}
|
||||
this.pushState();
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -396,7 +399,7 @@ export class Cluster implements ClusterModel {
|
||||
}
|
||||
|
||||
pushState = (state = this.getState()): ClusterState => {
|
||||
logger.debug(`[CLUSTER]: push-state`, state);
|
||||
logger.silly(`[CLUSTER]: push-state`, state);
|
||||
broadcastIpc({
|
||||
channel: "cluster:state",
|
||||
frameId: this.frameId,
|
||||
|
||||
@@ -70,7 +70,8 @@ export class ContextHandler {
|
||||
|
||||
async resolveAuthProxyUrl() {
|
||||
const proxyPort = await this.ensurePort();
|
||||
return `http://127.0.0.1:${proxyPort}`;
|
||||
const path = this.clusterUrl.path !== "/" ? this.clusterUrl.path : ""
|
||||
return `http://127.0.0.1:${proxyPort}${path}`;
|
||||
}
|
||||
|
||||
async getApiTarget(isWatchRequest = false): Promise<httpProxy.ServerOptions> {
|
||||
@@ -88,7 +89,7 @@ export class ContextHandler {
|
||||
protected async newApiTarget(timeout: number): Promise<httpProxy.ServerOptions> {
|
||||
const proxyUrl = await this.resolveAuthProxyUrl();
|
||||
return {
|
||||
target: proxyUrl + this.clusterUrl.path,
|
||||
target: proxyUrl,
|
||||
changeOrigin: true,
|
||||
timeout: timeout,
|
||||
headers: {
|
||||
|
||||
@@ -11,7 +11,7 @@ export class KubeconfigManager {
|
||||
protected configDir = app.getPath("temp")
|
||||
protected tempFile: string;
|
||||
|
||||
constructor(protected cluster: Cluster, protected contextHandler: ContextHandler) {
|
||||
constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,10 @@ export class KubeconfigManager {
|
||||
return this.tempFile;
|
||||
}
|
||||
|
||||
protected resolveProxyUrl() {
|
||||
return `http://127.0.0.1:${this.port}/${this.cluster.id}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -42,7 +46,7 @@ export class KubeconfigManager {
|
||||
clusters: [
|
||||
{
|
||||
name: contextName,
|
||||
server: await contextHandler.resolveAuthProxyUrl(),
|
||||
server: this.resolveProxyUrl(),
|
||||
skipTLSVerify: undefined,
|
||||
}
|
||||
],
|
||||
@@ -62,7 +66,7 @@ export class KubeconfigManager {
|
||||
// write
|
||||
const configYaml = dumpConfigYaml(proxyConfig);
|
||||
fs.ensureDir(path.dirname(tempFile));
|
||||
fs.writeFileSync(tempFile, configYaml);
|
||||
fs.writeFileSync(tempFile, configYaml, { mode: 0o600 });
|
||||
this.tempFile = tempFile;
|
||||
logger.debug(`Created temp kubeconfig "${contextName}" at "${tempFile}": \n${configYaml}`);
|
||||
return tempFile;
|
||||
|
||||
+40
-27
@@ -1,13 +1,14 @@
|
||||
import net from "net";
|
||||
import http from "http";
|
||||
import spdy from "spdy";
|
||||
import httpProxy from "http-proxy";
|
||||
import url from "url";
|
||||
import * as WebSocket from "ws"
|
||||
import { apiPrefix, apiKubePrefix } from "../common/vars"
|
||||
import { openShell } from "./node-shell-session";
|
||||
import { Router } from "./router"
|
||||
import { ClusterManager } from "./cluster-manager"
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import { apiKubePrefix } from "../common/vars";
|
||||
import logger from "./logger"
|
||||
|
||||
export class LensProxy {
|
||||
@@ -40,37 +41,49 @@ export class LensProxy {
|
||||
|
||||
protected buildCustomProxy(): http.Server {
|
||||
const proxy = this.createProxy();
|
||||
const customProxy = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
this.handleRequest(proxy, req, res);
|
||||
});
|
||||
customProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
||||
this.handleWsUpgrade(req, socket, head)
|
||||
});
|
||||
customProxy.on("error", (err) => {
|
||||
const spdyProxy = spdy.createServer({
|
||||
spdy: {
|
||||
plain: true,
|
||||
connection: {
|
||||
autoSpdy31: true
|
||||
}
|
||||
}
|
||||
}, (req: http.IncomingMessage, res: http.ServerResponse) => {
|
||||
this.handleRequest(proxy, req, res)
|
||||
})
|
||||
spdyProxy.on("upgrade", (req: http.IncomingMessage, socket: net.Socket, head: Buffer) => {
|
||||
if (req.url.startsWith(`${apiPrefix}?`)) {
|
||||
this.handleWsUpgrade(req, socket, head)
|
||||
} else {
|
||||
if (req.headers.upgrade?.startsWith("SPDY")) {
|
||||
this.handleSpdyProxy(proxy, req, socket, head)
|
||||
} else {
|
||||
socket.end()
|
||||
}
|
||||
}
|
||||
})
|
||||
spdyProxy.on("error", (err) => {
|
||||
logger.error("proxy error", err)
|
||||
});
|
||||
return customProxy;
|
||||
})
|
||||
return spdyProxy
|
||||
}
|
||||
|
||||
protected async handleSpdyProxy(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req)
|
||||
if (cluster) {
|
||||
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "")
|
||||
const apiUrl = url.parse(cluster.apiUrl)
|
||||
const res = new http.ServerResponse(req)
|
||||
res.assignSocket(socket)
|
||||
res.setHeader("Location", proxyUrl)
|
||||
res.setHeader("Host", apiUrl.hostname)
|
||||
res.statusCode = 302
|
||||
res.end()
|
||||
}
|
||||
}
|
||||
|
||||
protected createProxy(): httpProxy {
|
||||
const proxy = httpProxy.createProxyServer();
|
||||
proxy.on("proxyRes", (proxyRes, req, res) => {
|
||||
if (req.method !== "GET") {
|
||||
return;
|
||||
}
|
||||
if (proxyRes.statusCode === 502) {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req)
|
||||
const proxyError = cluster?.contextHandler.proxyLastError;
|
||||
if (proxyError) {
|
||||
return res.writeHead(502).end(proxyError);
|
||||
}
|
||||
}
|
||||
const reqId = this.getRequestId(req);
|
||||
if (this.retryCounters.has(reqId)) {
|
||||
logger.debug(`Resetting proxy retry cache for url: ${reqId}`);
|
||||
this.retryCounters.delete(reqId)
|
||||
}
|
||||
})
|
||||
proxy.on("error", (error, req, res, target) => {
|
||||
if (this.closed) {
|
||||
return;
|
||||
|
||||
+4
-2
@@ -2,14 +2,16 @@ import { app, remote } from "electron";
|
||||
import winston from "winston"
|
||||
import { isDebugging } from "../common/vars";
|
||||
|
||||
const logLevel = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : isDebugging ? "debug" : "info"
|
||||
|
||||
const consoleOptions: winston.transports.ConsoleTransportOptions = {
|
||||
handleExceptions: false,
|
||||
level: isDebugging ? "debug" : "info",
|
||||
level: logLevel,
|
||||
}
|
||||
|
||||
const fileOptions: winston.transports.FileTransportOptions = {
|
||||
handleExceptions: false,
|
||||
level: isDebugging ? "debug" : "info",
|
||||
level: logLevel,
|
||||
filename: "lens.log",
|
||||
dirname: (app ?? remote?.app)?.getPath("logs"),
|
||||
maxsize: 16 * 1024,
|
||||
|
||||
@@ -39,7 +39,7 @@ export class ShellSession extends EventEmitter {
|
||||
public async open() {
|
||||
this.kubectlBinDir = await this.kubectl.binDir()
|
||||
const pathFromPreferences = userStore.preferences.kubectlBinariesPath || Kubectl.bundledKubectlPath
|
||||
this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? await this.kubectl.binDir() : path.dirname(pathFromPreferences)
|
||||
this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? this.kubectlBinDir : path.dirname(pathFromPreferences)
|
||||
this.helmBinDir = helmCli.getBinaryDir()
|
||||
const env = await this.getCachedShellEnv()
|
||||
const shell = env.PTYSHELL
|
||||
|
||||
@@ -23,8 +23,8 @@ export class WindowManager {
|
||||
this.mainView = new BrowserWindow({
|
||||
x, y, width, height,
|
||||
show: false,
|
||||
minWidth: 900,
|
||||
minHeight: 760,
|
||||
minWidth: 700, // accommodate 800 x 600 display minimum
|
||||
minHeight: 500, // accommodate 800 x 600 display minimum
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "#1e2124",
|
||||
webPreferences: {
|
||||
|
||||
Reference in new issue
Block a user