mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
refactoring
Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
parent
8bb3954700
commit
e72e28ddab
@ -13,11 +13,12 @@ export type ClusterId = string;
|
||||
|
||||
export interface ClusterModel {
|
||||
id: ClusterId;
|
||||
contextName: string;
|
||||
kubeConfigPath: string;
|
||||
kubeConfig?: string;
|
||||
workspace?: string;
|
||||
preferences?: ClusterPreferences;
|
||||
kubeConfigPath: string;
|
||||
|
||||
/** @deprecated */
|
||||
kubeConfig?: string; // kube-config yaml
|
||||
}
|
||||
|
||||
export interface ClusterPreferences {
|
||||
|
||||
5
src/common/utils/cloneJson.ts
Normal file
5
src/common/utils/cloneJson.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// Clone json-serializable object
|
||||
|
||||
export function cloneJsonObject<T = object>(obj: T): T {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
@ -4,3 +4,4 @@ export * from "./base64"
|
||||
export * from "./camelCase"
|
||||
export * from "./splitArray"
|
||||
export * from "./randomFileName"
|
||||
export * from "./cloneJson"
|
||||
|
||||
@ -2,11 +2,8 @@ import { app, remote } from "electron"
|
||||
import { ensureDirSync, writeFileSync } from "fs-extra"
|
||||
import * as path from "path"
|
||||
|
||||
// todo: move to main/kubeconfig-manager.ts (?)
|
||||
|
||||
// Writes kubeconfigs to "embedded" store, i.e. .../Lens/kubeconfigs/
|
||||
// Write kubeconfigs to "embedded" store, i.e. "/Users/ixrock/Library/Application Support/Lens/kubeconfigs"
|
||||
export function writeEmbeddedKubeConfig(clusterId: string, kubeConfig: string): string {
|
||||
// This can be called from main & renderer
|
||||
const userData = (app || remote.app).getPath("userData");
|
||||
const kubeConfigBase = path.join(userData, "kubeconfigs")
|
||||
ensureDirSync(kubeConfigBase)
|
||||
|
||||
@ -89,6 +89,7 @@ export class ClusterManager {
|
||||
}
|
||||
}
|
||||
|
||||
// fixme
|
||||
getClusterForRequest(req: http.IncomingMessage): Cluster {
|
||||
let cluster: Cluster = null
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import url, { UrlWithStringQuery } from "url"
|
||||
import type { ClusterId, ClusterModel, ClusterPreferences } from "../common/cluster-store"
|
||||
import type { FeatureStatusMap } from "./feature"
|
||||
import { UrlWithStringQuery } from "url"
|
||||
import { action, observable, toJS } from "mobx";
|
||||
import { ContextHandler } from "./context-handler"
|
||||
import { AuthorizationV1Api, CoreV1Api, KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"
|
||||
@ -31,16 +31,15 @@ export interface ClusterState extends ClusterModel {
|
||||
}
|
||||
|
||||
export class Cluster implements ClusterModel {
|
||||
public id: ClusterId;
|
||||
public kubeCtl: Kubectl
|
||||
public contextHandler: ContextHandler;
|
||||
protected kubeconfigManager: KubeconfigManager;
|
||||
|
||||
@observable initialized = false;
|
||||
@observable id: ClusterId;
|
||||
@observable workspace: string;
|
||||
@observable kubeConfig?: string;
|
||||
@observable kubeConfigPath: string;
|
||||
@observable contextName: string;
|
||||
@observable kubeConfigPath: string;
|
||||
@observable port: number;
|
||||
@observable url: string; // cluster-api url
|
||||
@observable apiUrl: UrlWithStringQuery; // same as url, but parsed
|
||||
@ -63,20 +62,23 @@ export class Cluster implements ClusterModel {
|
||||
|
||||
updateModel(model: ClusterModel) {
|
||||
Object.assign(this, model);
|
||||
if (!this.contextName) {
|
||||
this.contextName = this.preferences.clusterName;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
// fixme: completely broken
|
||||
async init() {
|
||||
try {
|
||||
// fixme: all broken
|
||||
this.contextHandler = new ContextHandler(this);
|
||||
this.contextName = this.contextHandler.contextName;
|
||||
this.port = await this.contextHandler.resolveProxyPort(); // resolve port before KubeconfigManager
|
||||
this.webContentUrl = `http://${this.id}.localhost:${this.port}`;
|
||||
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.url = this.kubeconfigManager.getCurrentClusterServer();
|
||||
// this.apiUrl = url.parse(this.url);
|
||||
|
||||
logger.info(`[CLUSTER]: init success`, {
|
||||
id: this.id,
|
||||
port: this.port,
|
||||
@ -102,7 +104,7 @@ export class Cluster implements ClusterModel {
|
||||
// todo: auto-refresh when preferences changed + by timer?
|
||||
@action
|
||||
async refreshCluster() {
|
||||
this.contextHandler.setClusterPreferences(this.preferences);
|
||||
this.contextHandler.setupPrometheus(this.preferences);
|
||||
|
||||
const connectionStatus = await this.getConnectionStatus()
|
||||
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
|
||||
|
||||
@ -9,29 +9,18 @@ import { getFreePort } from "./port"
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy"
|
||||
|
||||
export class ContextHandler {
|
||||
public url: string
|
||||
public proxyPort: number
|
||||
public contextName: string
|
||||
|
||||
protected id: string
|
||||
protected proxyServer: KubeAuthProxy
|
||||
protected apiTarget: ServerOptions
|
||||
protected certData: string
|
||||
protected authCertData: string
|
||||
protected proxyTarget: ServerOptions
|
||||
protected clientCert: string
|
||||
protected clientKey: string
|
||||
protected prometheusProvider: string
|
||||
protected prometheusPath: string
|
||||
|
||||
constructor(protected cluster: Cluster) {
|
||||
this.id = cluster.id
|
||||
this.url = cluster.url;
|
||||
this.contextName = cluster.contextName || cluster.preferences.clusterName;
|
||||
this.setClusterPreferences(cluster.preferences)
|
||||
this.setupPrometheus(cluster.preferences)
|
||||
}
|
||||
|
||||
public setClusterPreferences(preferences: ClusterPreferences = {}) {
|
||||
public setupPrometheus(preferences: ClusterPreferences = {}) {
|
||||
this.prometheusProvider = preferences.prometheusProvider?.type;
|
||||
this.prometheusPath = null;
|
||||
if (preferences.prometheus) {
|
||||
@ -89,6 +78,7 @@ export class ContextHandler {
|
||||
return apiTarget
|
||||
}
|
||||
|
||||
// fixme
|
||||
protected async newApiTarget(timeout: number): Promise<ServerOptions> {
|
||||
return {
|
||||
changeOrigin: true,
|
||||
@ -107,27 +97,19 @@ export class ContextHandler {
|
||||
|
||||
async resolveProxyPort(): Promise<number> {
|
||||
if (!this.proxyPort) {
|
||||
this.proxyPort = await getFreePort()
|
||||
this.proxyPort = await getFreePort();
|
||||
}
|
||||
return this.proxyPort
|
||||
}
|
||||
|
||||
public async withTemporaryKubeconfig(callback: (kubeconfig: string) => Promise<any>) {
|
||||
try {
|
||||
await callback(this.cluster.proxyKubeconfigPath())
|
||||
} catch (error) {
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
|
||||
public async ensureServer() {
|
||||
if (!this.proxyServer) {
|
||||
const proxyPort = await this.resolveProxyPort()
|
||||
await this.resolveProxyPort();
|
||||
const proxyEnv = Object.assign({}, process.env)
|
||||
if (this.cluster.preferences.httpsProxy) {
|
||||
proxyEnv.HTTPS_PROXY = this.cluster.preferences.httpsProxy
|
||||
}
|
||||
this.proxyServer = new KubeAuthProxy(this.cluster, proxyPort, proxyEnv)
|
||||
this.proxyServer = new KubeAuthProxy(this.cluster, this.proxyPort, proxyEnv)
|
||||
await this.proxyServer.run()
|
||||
}
|
||||
}
|
||||
@ -139,7 +121,7 @@ export class ContextHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public proxyServerError() {
|
||||
public proxyServerError(): string {
|
||||
return this.proxyServer?.lastError || ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import fs from "fs";
|
||||
import path from "path"
|
||||
import hb from "handlebars"
|
||||
import { ResourceApplier } from "./resource-applier"
|
||||
import { KubeConfig, CoreV1Api, Watch } from "@kubernetes/client-node"
|
||||
import { CoreV1Api, KubeConfig, Watch } from "@kubernetes/client-node"
|
||||
import { Cluster } from "./cluster";
|
||||
import logger from "./logger";
|
||||
|
||||
@ -23,75 +23,61 @@ export interface FeatureStatus {
|
||||
|
||||
export abstract class Feature {
|
||||
name: string;
|
||||
config: any;
|
||||
latestVersion: string;
|
||||
|
||||
constructor(config: any) {
|
||||
if(config) this.config = config;
|
||||
}
|
||||
|
||||
// TODO Return types for these?
|
||||
async install(cluster: Cluster): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
// Read and process yamls through handlebar
|
||||
const resources = this.renderTemplates();
|
||||
|
||||
// Apply processed manifests
|
||||
cluster.contextHandler.withTemporaryKubeconfig(async (kubeconfigPath) => {
|
||||
const resourceApplier = new ResourceApplier(cluster, kubeconfigPath)
|
||||
try {
|
||||
await resourceApplier.kubectlApplyAll(resources)
|
||||
resolve(true)
|
||||
} catch(error) {
|
||||
reject(error)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
abstract async upgrade(cluster: Cluster): Promise<boolean>;
|
||||
|
||||
abstract async uninstall(cluster: Cluster): Promise<boolean>;
|
||||
|
||||
abstract async featureStatus(kc: KubeConfig): Promise<FeatureStatus>;
|
||||
|
||||
constructor(public config: any) {
|
||||
}
|
||||
|
||||
async install(cluster: Cluster): Promise<boolean> {
|
||||
const resources = this.renderTemplates();
|
||||
try {
|
||||
await new ResourceApplier(cluster).kubectlApplyAll(resources);
|
||||
return true;
|
||||
} catch (err) {
|
||||
logger.error("Installing feature error", { err, cluster });
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
protected async deleteNamespace(kc: KubeConfig, name: string) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const client = kc.makeApiClient(CoreV1Api)
|
||||
const result = await client.deleteNamespace("lens-metrics", 'false', undefined, undefined, undefined, "Foreground");
|
||||
const nsVersion = result.body.metadata.resourceVersion;
|
||||
const nsWatch = new Watch(kc);
|
||||
const req = await nsWatch.watch('/api/v1/namespaces', {resourceVersion: nsVersion, fieldSelector: "metadata.name=lens-metrics"},
|
||||
(type, obj) => {
|
||||
if(type === 'DELETED') {
|
||||
const query: Record<string, string> = {
|
||||
resourceVersion: nsVersion,
|
||||
fieldSelector: "metadata.name=lens-metrics",
|
||||
}
|
||||
const req = await nsWatch.watch('/api/v1/namespaces', query,
|
||||
(phase, obj) => {
|
||||
if (phase === 'DELETED') {
|
||||
logger.debug(`namespace ${name} finally gone`)
|
||||
req.abort();
|
||||
resolve()
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
if(err) {
|
||||
reject(err)
|
||||
}
|
||||
(err?: any) => {
|
||||
if (err) reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected renderTemplates(): string[] {
|
||||
console.log("starting to render resources...");
|
||||
const resources: string[] = [];
|
||||
fs.readdirSync(this.manifestPath()).forEach((f) => {
|
||||
const file = path.join(this.manifestPath(), f);
|
||||
console.log("processing file:", file)
|
||||
fs.readdirSync(this.manifestPath()).forEach(filename => {
|
||||
const file = path.join(this.manifestPath(), filename);
|
||||
const raw = fs.readFileSync(file);
|
||||
console.log("raw file loaded");
|
||||
if(f.endsWith('.hb')) {
|
||||
console.log("processing HB template");
|
||||
if (filename.endsWith('.hb')) {
|
||||
const template = hb.compile(raw.toString());
|
||||
resources.push(template(this.config));
|
||||
console.log("HB template done");
|
||||
} else {
|
||||
console.log("using as raw, no HB detected");
|
||||
resources.push(raw.toString());
|
||||
}
|
||||
});
|
||||
@ -101,7 +87,7 @@ export abstract class Feature {
|
||||
|
||||
protected manifestPath() {
|
||||
const devPath = path.join(__dirname, "..", 'src/features', this.name);
|
||||
if(fs.existsSync(devPath)) {
|
||||
if (fs.existsSync(devPath)) {
|
||||
return devPath;
|
||||
}
|
||||
return path.join(__dirname, "..", 'features', this.name);
|
||||
|
||||
@ -99,7 +99,7 @@ export class HelmReleaseManager {
|
||||
|
||||
protected async getResources(name: string, namespace: string, cluster: Cluster) {
|
||||
const helm = await helmCli.binaryPath()
|
||||
const kubectl = await cluster.kubeCtl.kubectlPath()
|
||||
const kubectl = await cluster.kubeCtl.getPath()
|
||||
const pathToKubeconfig = cluster.proxyKubeconfigPath()
|
||||
const { stdout } = await promiseExec(`"${helm}" get manifest ${name} --namespace ${namespace} --kubeconfig ${pathToKubeconfig} | "${kubectl}" get -n ${namespace} --kubeconfig ${pathToKubeconfig} -f - -o=json`).catch((error) => {
|
||||
return { stdout: JSON.stringify({items: []})}
|
||||
|
||||
@ -11,7 +11,7 @@ function resolveTilde(filePath: string) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
export function loadKubeConfig(pathOrContent?: string): KubeConfig {
|
||||
export function loadConfig(pathOrContent?: string): KubeConfig {
|
||||
const kc = new KubeConfig();
|
||||
if (path.isAbsolute(pathOrContent)) {
|
||||
kc.loadFromFile(resolveTilde(pathOrContent));
|
||||
@ -30,7 +30,7 @@ export function loadKubeConfig(pathOrContent?: string): KubeConfig {
|
||||
*/
|
||||
export function validateConfig(config: KubeConfig | string): KubeConfig {
|
||||
if (typeof config == "string") {
|
||||
config = loadKubeConfig(config);
|
||||
config = loadConfig(config);
|
||||
}
|
||||
logger.debug(`validating kube config: ${JSON.stringify(config)}`)
|
||||
if (!config.users || config.users.length == 0) {
|
||||
@ -66,17 +66,6 @@ export function splitConfig(kubeConfig: KubeConfig): KubeConfig[] {
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads KubeConfig from a yaml and breaks it into several configs. Each context per KubeConfig object
|
||||
*
|
||||
* @param configPath path to kube config yaml file
|
||||
*/
|
||||
export function loadAndSplitConfig(configPath: string): KubeConfig[] {
|
||||
const allConfigs = new KubeConfig();
|
||||
allConfigs.loadFromFile(configPath);
|
||||
return splitConfig(allConfigs);
|
||||
}
|
||||
|
||||
export function dumpConfigYaml(kc: KubeConfig): string {
|
||||
const config = {
|
||||
apiVersion: "v1",
|
||||
@ -122,8 +111,7 @@ export function dumpConfigYaml(kc: KubeConfig): string {
|
||||
})
|
||||
}
|
||||
|
||||
console.log("dumping kc:", config);
|
||||
|
||||
// logger.info("Dumping KubeConfig:", config);
|
||||
// skipInvalid: true makes dump ignore undefined values
|
||||
return yaml.safeDump(config, { skipInvalid: true });
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ export class KubeAuthProxy {
|
||||
if (this.proxyProcess) {
|
||||
return;
|
||||
}
|
||||
const proxyBin = await this.kubectl.kubectlPath()
|
||||
const proxyBin = await this.kubectl.getPath()
|
||||
let args = [
|
||||
"proxy",
|
||||
"-p", this.port.toString(),
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import type { Cluster } from "./cluster"
|
||||
import { app } from "electron"
|
||||
import fs from "fs-extra"
|
||||
import { KubeConfig } from "@kubernetes/client-node"
|
||||
import { randomFileName } from "../common/utils"
|
||||
import { dumpConfigYaml, loadKubeConfig } from "./k8s"
|
||||
import { dumpConfigYaml, loadConfig } from "./k8s"
|
||||
import logger from "./logger"
|
||||
|
||||
export class KubeconfigManager {
|
||||
public config: KubeConfig;
|
||||
protected configDir = app.getPath("temp")
|
||||
protected tempFile: string
|
||||
|
||||
@ -19,16 +17,6 @@ export class KubeconfigManager {
|
||||
return this.tempFile;
|
||||
}
|
||||
|
||||
getCurrentClusterServer() {
|
||||
return this.config.getCurrentCluster().server;
|
||||
}
|
||||
|
||||
protected loadConfig() {
|
||||
const { kubeConfigPath, kubeConfig } = this.cluster;
|
||||
this.config = loadKubeConfig(kubeConfigPath || kubeConfig);
|
||||
return this.config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -36,12 +24,12 @@ export class KubeconfigManager {
|
||||
protected createTemporaryKubeconfig(): string {
|
||||
fs.ensureDir(this.configDir);
|
||||
const path = `${this.configDir}/${randomFileName("kubeconfig")}`;
|
||||
const { contextName, kubeAuthProxyUrl } = this.cluster;
|
||||
const kubeConfig = this.loadConfig();
|
||||
const { contextName, contextHandler, kubeConfigPath } = this.cluster;
|
||||
const kubeConfig = loadConfig(kubeConfigPath);
|
||||
kubeConfig.clusters = [
|
||||
{
|
||||
name: contextName,
|
||||
server: kubeAuthProxyUrl,
|
||||
server: `http://127.0.0.1:${contextHandler.proxyPort}`, // fixme: extract
|
||||
skipTLSVerify: true,
|
||||
}
|
||||
];
|
||||
@ -54,10 +42,11 @@ export class KubeconfigManager {
|
||||
user: "proxy",
|
||||
name: contextName,
|
||||
cluster: contextName,
|
||||
// namespace: kubeConfig.getContextObject(contextName).namespace,
|
||||
}
|
||||
];
|
||||
logger.info(`Creating temp config for context "${contextName}" at "${path}"`);
|
||||
fs.writeFileSync(path, dumpConfigYaml(kubeConfig));
|
||||
logger.info(`Created temp kube-config file for context "${this.cluster.contextName}" at "${path}"`);
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ export class Kubectl {
|
||||
this.path = path.join(this.dirname, binaryName)
|
||||
}
|
||||
|
||||
public async kubectlPath(): Promise<string> {
|
||||
public async getPath(): Promise<string> {
|
||||
try {
|
||||
await this.ensureKubectl()
|
||||
return this.path
|
||||
|
||||
@ -4,7 +4,7 @@ import { Socket } from "net";
|
||||
import * as url from "url";
|
||||
import * as WebSocket from "ws"
|
||||
import { ContextHandler } from "./context-handler";
|
||||
import * as shell from "./node-shell-session"
|
||||
import * as nodeShell from "./node-shell-session"
|
||||
import { ClusterManager } from "./cluster-manager"
|
||||
import { Router } from "./router"
|
||||
import { apiPrefix } from "../common/vars";
|
||||
@ -84,12 +84,13 @@ export class LensProxy {
|
||||
if (req.method === "GET" && (!res.statusCode || res.statusCode >= 500)) {
|
||||
const retryCounterKey = `${req.headers.host}${req.url}`
|
||||
const retryCount = this.retryCounters.get(retryCounterKey) || 0
|
||||
const timeoutMs = retryCount * 250
|
||||
if (retryCount < 20) {
|
||||
logger.debug("Retrying proxy request to url: " + retryCounterKey)
|
||||
setTimeout(() => {
|
||||
this.retryCounters.set(retryCounterKey, retryCount + 1)
|
||||
this.handleRequest(proxy, req, res)
|
||||
}, (250 * retryCount))
|
||||
}, timeoutMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,23 +103,13 @@ export class LensProxy {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
protected createWsListener() {
|
||||
protected createWsListener(): WebSocket.Server {
|
||||
const ws = new WebSocket.Server({ noServer: true })
|
||||
ws.on("connection", ((con: WebSocket, req: http.IncomingMessage) => {
|
||||
return ws.on("connection", (async (socket: WebSocket, req: http.IncomingMessage) => {
|
||||
const cluster = this.clusterManager.getClusterForRequest(req)
|
||||
const contextHandler = cluster.contextHandler
|
||||
const nodeParam = url.parse(req.url, true).query["node"]?.toString();
|
||||
|
||||
contextHandler.withTemporaryKubeconfig((kubeconfigPath) => {
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
const shellSession = await shell.open(con, kubeconfigPath, cluster, nodeParam)
|
||||
shellSession.on("exit", () => {
|
||||
resolve(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
}))
|
||||
return ws
|
||||
await nodeShell.open(socket, cluster, nodeParam);
|
||||
}));
|
||||
}
|
||||
|
||||
protected async getProxyTarget(req: http.IncomingMessage, contextHandler: ContextHandler): Promise<httpProxy.ServerOptions> {
|
||||
@ -146,7 +137,7 @@ export class LensProxy {
|
||||
if (proxyTarget) {
|
||||
proxy.web(req, res, proxyTarget)
|
||||
} else {
|
||||
this.router.route(cluster, req, res); // todo: handle not-found route when isBoom==true?
|
||||
this.router.route(cluster, req, res); // todo: handle "not-found" if isBoom==true?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,15 +13,15 @@ export class NodeShellSession extends ShellSession {
|
||||
protected podId: string
|
||||
protected kc: KubeConfig
|
||||
|
||||
constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName: string) {
|
||||
super(socket, pathToKubeconfig, cluster)
|
||||
constructor(socket: WebSocket, cluster: Cluster, nodeName: string) {
|
||||
super(socket, cluster)
|
||||
this.nodeName = nodeName
|
||||
this.podId = `node-shell-${uuid()}`
|
||||
this.kc = cluster.proxyKubeconfig()
|
||||
}
|
||||
|
||||
public async open() {
|
||||
const shell = await this.kubectl.kubectlPath()
|
||||
const shell = await this.kubectl.getPath()
|
||||
let args = []
|
||||
if (this.createNodeShellPod(this.podId, this.nodeName)) {
|
||||
await this.waitForRunningPod(this.podId).catch((error) => {
|
||||
@ -133,15 +133,9 @@ export class NodeShellSession extends ShellSession {
|
||||
}
|
||||
}
|
||||
|
||||
export async function open(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster, nodeName?: string): Promise<ShellSession> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let shell = null
|
||||
if (nodeName) {
|
||||
shell = new NodeShellSession(socket, pathToKubeconfig, cluster, nodeName)
|
||||
} else {
|
||||
shell = new ShellSession(socket, pathToKubeconfig, cluster)
|
||||
}
|
||||
shell.open()
|
||||
resolve(shell)
|
||||
})
|
||||
export async function open(socket: WebSocket, cluster: Cluster, nodeName?: string): Promise<ShellSession> {
|
||||
if (nodeName) {
|
||||
return new NodeShellSession(socket, cluster, nodeName)
|
||||
}
|
||||
return new ShellSession(socket, cluster);
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import logger from "./logger"
|
||||
import { createServer, AddressInfo } from "net"
|
||||
|
||||
// todo: replace with https://github.com/http-party/node-portfinder ?
|
||||
|
||||
const getNextAvailablePort = () => {
|
||||
logger.debug("getNextAvailablePort() start")
|
||||
const server = createServer()
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import { LensApiRequest } from "./router"
|
||||
import * as resourceApplier from "./resource-applier"
|
||||
import { ResourceApplier } from "./resource-applier"
|
||||
import { LensApi } from "./lens-api"
|
||||
|
||||
class ResourceApplierApi extends LensApi {
|
||||
public async applyResource(request: LensApiRequest) {
|
||||
const { response, cluster, payload } = request
|
||||
try {
|
||||
const resource = await resourceApplier.apply(cluster, cluster.proxyKubeconfigPath(), payload)
|
||||
const resource = await new ResourceApplier(cluster).apply(payload);
|
||||
this.respondJson(response, [resource], 200)
|
||||
} catch(error) {
|
||||
} catch (error) {
|
||||
this.respondText(response, error, 422)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +1,31 @@
|
||||
import type { Cluster } from "./cluster";
|
||||
import { KubernetesObject } from "@kubernetes/client-node"
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import * as yaml from "js-yaml";
|
||||
import path from "path";
|
||||
import * as tempy from "tempy";
|
||||
import logger from "./logger"
|
||||
import { Cluster } from "./cluster";
|
||||
import { tracker } from "../common/tracker";
|
||||
|
||||
type KubeObject = {
|
||||
status: {};
|
||||
metadata?: {
|
||||
resourceVersion: number;
|
||||
annotations?: {
|
||||
"kubectl.kubernetes.io/last-applied-configuration": string;
|
||||
};
|
||||
};
|
||||
}
|
||||
import { cloneJsonObject } from "../common/utils";
|
||||
|
||||
export class ResourceApplier {
|
||||
protected kubeconfigPath: string;
|
||||
protected cluster: Cluster
|
||||
|
||||
constructor(cluster: Cluster, pathToKubeconfig: string) {
|
||||
this.kubeconfigPath = pathToKubeconfig
|
||||
this.cluster = cluster
|
||||
constructor(protected cluster: Cluster) {
|
||||
}
|
||||
|
||||
public async apply(resource: any): Promise<string> {
|
||||
this.sanitizeObject((resource as KubeObject))
|
||||
try {
|
||||
tracker.event("resource", "apply")
|
||||
return await this.kubectlApply(yaml.safeDump(resource))
|
||||
} catch(error) {
|
||||
throw (error)
|
||||
}
|
||||
async apply(resource: KubernetesObject | any): Promise<string> {
|
||||
resource = this.sanitizeObject(resource);
|
||||
tracker.event("resource", "apply")
|
||||
return await this.kubectlApply(yaml.safeDump(resource));
|
||||
}
|
||||
|
||||
protected async kubectlApply(content: string): Promise<string> {
|
||||
const kubectl = await this.cluster.kubeCtl.kubectlPath()
|
||||
const { kubeCtl, kubeConfigPath } = this.cluster;
|
||||
const kubectlPath = await kubeCtl.getPath()
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const fileName = tempy.file({name: "resource.yaml"})
|
||||
const fileName = tempy.file({ name: "resource.yaml" })
|
||||
fs.writeFileSync(fileName, content)
|
||||
const cmd = `"${kubectl}" apply --kubeconfig ${this.kubeconfigPath} -o json -f ${fileName}`
|
||||
const cmd = `"${kubectlPath}" apply --kubeconfig ${kubeConfigPath} -o json -f ${fileName}`
|
||||
logger.debug("shooting manifests with: " + cmd);
|
||||
const execEnv: NodeJS.ProcessEnv = Object.assign({}, process.env)
|
||||
const httpsProxy = this.cluster.preferences?.httpsProxy
|
||||
@ -62,17 +46,18 @@ export class ResourceApplier {
|
||||
}
|
||||
|
||||
public async kubectlApplyAll(resources: string[]): Promise<string> {
|
||||
const kubectl = await this.cluster.kubeCtl.kubectlPath()
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const { kubeCtl, kubeConfigPath } = this.cluster;
|
||||
const kubectlPath = await kubeCtl.getPath()
|
||||
return new Promise((resolve, reject) => {
|
||||
const tmpDir = tempy.directory()
|
||||
// Dump each resource into tmpDir
|
||||
for (const i in resources) {
|
||||
fs.writeFileSync(path.join(tmpDir, `${i}.yaml`), resources[i])
|
||||
}
|
||||
const cmd = `"${kubectl}" apply --kubeconfig ${this.kubeconfigPath} -o json -f ${tmpDir}`
|
||||
resources.forEach((resource, index) => {
|
||||
fs.writeFileSync(path.join(tmpDir, `${index}.yaml`), resource);
|
||||
})
|
||||
const cmd = `"${kubectlPath}" apply --kubeconfig ${kubeConfigPath} -o json -f ${tmpDir}`
|
||||
console.log("shooting manifests with:", cmd);
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
if(error) {
|
||||
if (error) {
|
||||
reject("Error applying manifests:" + error);
|
||||
}
|
||||
if (stderr != "") {
|
||||
@ -84,18 +69,14 @@ export class ResourceApplier {
|
||||
})
|
||||
}
|
||||
|
||||
protected sanitizeObject(resource: KubeObject) {
|
||||
delete resource['status']
|
||||
if (resource['metadata']) {
|
||||
if (resource['metadata']['annotations'] && resource['metadata']['annotations']['kubectl.kubernetes.io/last-applied-configuration']) {
|
||||
delete resource['metadata']['annotations']['kubectl.kubernetes.io/last-applied-configuration']
|
||||
}
|
||||
delete resource['metadata']['resourceVersion']
|
||||
protected sanitizeObject(resource: KubernetesObject | any) {
|
||||
resource = cloneJsonObject(resource);
|
||||
delete resource.status;
|
||||
delete resource.metadata?.resourceVersion;
|
||||
const annotations = resource.metadata?.annotations;
|
||||
if (annotations) {
|
||||
delete annotations['kubectl.kubernetes.io/last-applied-configuration'];
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(cluster: Cluster, pathToKubeconfig: string, resource: any) {
|
||||
const resourceApplier = new ResourceApplier(cluster, pathToKubeconfig)
|
||||
return await resourceApplier.apply(resource)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ 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;
|
||||
@ -11,6 +12,7 @@ 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}`,
|
||||
@ -25,7 +27,7 @@ class MetricsRoute extends LensApi {
|
||||
let prometheusProvider: PrometheusProvider
|
||||
try {
|
||||
const prometheusPath = await cluster.contextHandler.getPrometheusPath()
|
||||
metricsUrl = `${cluster.kubeAuthProxyUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range`
|
||||
metricsUrl = `${serverUrl}/api/v1/namespaces/${prometheusPath}/proxy${cluster.getPrometheusApiPrefix()}/api/v1/query_range`
|
||||
prometheusProvider = await cluster.contextHandler.getPrometheusProvider()
|
||||
} catch {
|
||||
this.respondJson(response, {})
|
||||
|
||||
@ -37,7 +37,7 @@ class PortForward {
|
||||
|
||||
public async start() {
|
||||
this.localPort = await getFreePort()
|
||||
const kubectlBin = await bundledKubectl.kubectlPath()
|
||||
const kubectlBin = await bundledKubectl.getPath()
|
||||
const args = [
|
||||
"--kubeconfig", this.kubeConfig,
|
||||
"port-forward",
|
||||
|
||||
@ -25,10 +25,10 @@ export class ShellSession extends EventEmitter {
|
||||
protected running = false;
|
||||
protected clusterId: string;
|
||||
|
||||
constructor(socket: WebSocket, pathToKubeconfig: string, cluster: Cluster) {
|
||||
constructor(socket: WebSocket, cluster: Cluster) {
|
||||
super()
|
||||
this.websocket = socket
|
||||
this.kubeconfigPath = pathToKubeconfig
|
||||
this.kubeconfigPath = cluster.kubeConfigPath
|
||||
this.kubectl = new Kubectl(cluster.version)
|
||||
this.preferences = cluster.preferences || {}
|
||||
this.clusterId = cluster.id
|
||||
|
||||
@ -4,40 +4,33 @@ import path from "path"
|
||||
import { app, remote } from "electron"
|
||||
import { migration } from "../migration-wrapper";
|
||||
import { ensureDirSync } from "fs-extra"
|
||||
import { KubeConfig } from "@kubernetes/client-node";
|
||||
import { writeEmbeddedKubeConfig } from "../../common/utils/kubeconfig"
|
||||
import { ClusterModel } from "../../common/cluster-store";
|
||||
|
||||
export default migration({
|
||||
version: "3.6.0-beta.1",
|
||||
run(store, log: (...args: any[]) => void) {
|
||||
const migratingClusters: ClusterModel[] = []
|
||||
|
||||
const migratedClusters: ClusterModel[] = []
|
||||
const storedClusters: ClusterModel[] = store.get("clusters");
|
||||
const kubeConfigBase = path.join((app || remote.app).getPath("userData"), "kubeconfigs")
|
||||
ensureDirSync(kubeConfigBase)
|
||||
const storedClusters: ClusterModel[] = store.get("clusters")
|
||||
if (!storedClusters) return
|
||||
|
||||
if (!storedClusters) return;
|
||||
ensureDirSync(kubeConfigBase);
|
||||
|
||||
log("Number of clusters to migrate: ", storedClusters.length)
|
||||
for (const cluster of storedClusters) {
|
||||
try {
|
||||
// take the embedded kubeconfig and dump it into a file
|
||||
cluster.kubeConfigPath = writeEmbeddedKubeConfig(cluster.id, cluster.kubeConfig)
|
||||
|
||||
const kc = new KubeConfig()
|
||||
kc.loadFromFile(cluster.kubeConfigPath)
|
||||
cluster.contextName = kc.getCurrentContext()
|
||||
|
||||
delete cluster.kubeConfig
|
||||
migratingClusters.push(cluster)
|
||||
migratedClusters.push(cluster)
|
||||
} catch (error) {
|
||||
log(`Failed to migrate Kubeconfig for cluster "${cluster.id}"`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// "overwrite" the cluster configs
|
||||
if (migratingClusters.length > 0) {
|
||||
store.set("clusters", migratingClusters)
|
||||
if (migratedClusters.length > 0) {
|
||||
store.set("clusters", migratedClusters)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user