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:
Roman committed 2020-09-23 11:49:16 +03:00
commit 91b4823ec6
54 files changed
+619 -333

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -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,
+3 -2
View File
@@ -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: {
+7 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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: {