mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into extensions-api
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
commit
a00d26149c
54 files changed
+1548
-913
No files matched your search
@@ -0,0 +1,165 @@
|
||||
const logger = {
|
||||
silly: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
crit: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock("winston", () => ({
|
||||
format: {
|
||||
colorize: jest.fn(),
|
||||
combine: jest.fn(),
|
||||
simple: jest.fn(),
|
||||
label: jest.fn(),
|
||||
timestamp: jest.fn(),
|
||||
printf: jest.fn()
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue(logger),
|
||||
transports: {
|
||||
Console: jest.fn(),
|
||||
File: jest.fn(),
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
jest.mock("../../common/ipc")
|
||||
jest.mock("../context-handler")
|
||||
jest.mock("request")
|
||||
jest.mock("request-promise-native")
|
||||
|
||||
import { Console } from "console";
|
||||
import mockFs from "mock-fs";
|
||||
import { workspaceStore } from "../../common/workspace-store";
|
||||
import { Cluster } from "../cluster"
|
||||
import { ContextHandler } from "../context-handler";
|
||||
import { getFreePort } from "../port";
|
||||
import { V1ResourceAttributes } from "@kubernetes/client-node";
|
||||
import { apiResources } from "../../common/rbac";
|
||||
import request from "request-promise-native"
|
||||
|
||||
const mockedRequest = request as jest.MockedFunction<typeof request>
|
||||
|
||||
console = new Console(process.stdout, process.stderr) // fix mockFS
|
||||
|
||||
describe("create clusters", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
"minikube-config.yml": JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: "https://192.168.64.3:8443",
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
}],
|
||||
users: [{
|
||||
name: "minikube",
|
||||
}],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
})
|
||||
}
|
||||
mockFs(mockOpts)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
it("should be able to create a cluster from a cluster model and apiURL should be decoded", () => {
|
||||
const c = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
workspace: workspaceStore.currentWorkspaceId
|
||||
})
|
||||
expect(c.apiUrl).toBe("https://192.168.64.3:8443")
|
||||
})
|
||||
|
||||
it("init should not throw if everything is in order", async () => {
|
||||
const c = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
workspace: workspaceStore.currentWorkspaceId
|
||||
})
|
||||
await c.init(await getFreePort())
|
||||
expect(logger.info).toBeCalledWith(expect.stringContaining("init success"), {
|
||||
id: "foo",
|
||||
apiUrl: "https://192.168.64.3:8443",
|
||||
context: "minikube",
|
||||
})
|
||||
})
|
||||
|
||||
it("activating cluster should try to connect to cluster and do a refresh", async () => {
|
||||
const port = await getFreePort()
|
||||
jest.spyOn(ContextHandler.prototype, "ensureServer");
|
||||
|
||||
const mockListNSs = jest.fn()
|
||||
const mockKC = {
|
||||
makeApiClient() {
|
||||
return {
|
||||
listNamespace: mockListNSs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
jest.spyOn(Cluster.prototype, "canI")
|
||||
.mockImplementationOnce((attr: V1ResourceAttributes): Promise<boolean> => {
|
||||
expect(attr.namespace).toBe("default")
|
||||
expect(attr.resource).toBe("pods")
|
||||
expect(attr.verb).toBe("list")
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
.mockImplementation((attr: V1ResourceAttributes): Promise<boolean> => {
|
||||
expect(attr.namespace).toBe("default")
|
||||
expect(attr.verb).toBe("list")
|
||||
return Promise.resolve(true)
|
||||
})
|
||||
jest.spyOn(Cluster.prototype, "getProxyKubeconfig").mockReturnValue(mockKC as any)
|
||||
mockListNSs.mockImplementationOnce(() => ({
|
||||
body: {
|
||||
items: [{
|
||||
metadata: {
|
||||
name: "default",
|
||||
}
|
||||
}]
|
||||
}
|
||||
}))
|
||||
|
||||
mockedRequest.mockImplementationOnce(((uri: any, _options: any) => {
|
||||
expect(uri).toBe(`http://localhost:${port}/api-kube/version`)
|
||||
return Promise.resolve({ gitVersion: "1.2.3" })
|
||||
}) as any)
|
||||
|
||||
const c = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
workspace: workspaceStore.currentWorkspaceId
|
||||
})
|
||||
await c.init(port)
|
||||
await c.activate()
|
||||
|
||||
expect(ContextHandler.prototype.ensureServer).toBeCalled()
|
||||
expect(mockedRequest).toBeCalled()
|
||||
expect(c.accessible).toBe(true)
|
||||
expect(c.allowedNamespaces.length).toBe(1)
|
||||
expect(c.allowedResources.length).toBe(apiResources.length)
|
||||
|
||||
jest.resetAllMocks()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,130 @@
|
||||
const logger = {
|
||||
silly: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
crit: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock("winston", () => ({
|
||||
format: {
|
||||
colorize: jest.fn(),
|
||||
combine: jest.fn(),
|
||||
simple: jest.fn(),
|
||||
label: jest.fn(),
|
||||
timestamp: jest.fn(),
|
||||
printf: jest.fn()
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue(logger),
|
||||
transports: {
|
||||
Console: jest.fn(),
|
||||
File: jest.fn(),
|
||||
}
|
||||
}))
|
||||
|
||||
jest.mock("../../common/ipc")
|
||||
jest.mock("child_process")
|
||||
jest.mock("tcp-port-used")
|
||||
|
||||
import { Cluster } from "../cluster"
|
||||
import { KubeAuthProxy } from "../kube-auth-proxy"
|
||||
import { getFreePort } from "../port"
|
||||
import { broadcastIpc } from "../../common/ipc"
|
||||
import { ChildProcess, spawn, SpawnOptions } from "child_process"
|
||||
import { bundledKubectlPath, Kubectl } from "../kubectl"
|
||||
import { mock, MockProxy } from 'jest-mock-extended';
|
||||
import { waitUntilUsed } from 'tcp-port-used';
|
||||
import { Readable } from "stream"
|
||||
|
||||
const mockBroadcastIpc = broadcastIpc as jest.MockedFunction<typeof broadcastIpc>
|
||||
const mockSpawn = spawn as jest.MockedFunction<typeof spawn>
|
||||
const mockWaitUntilUsed = waitUntilUsed as jest.MockedFunction<typeof waitUntilUsed>
|
||||
|
||||
describe("kube auth proxy tests", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calling exit multiple times shouldn't throw", async () => {
|
||||
const port = await getFreePort()
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
kap.exit()
|
||||
kap.exit()
|
||||
kap.exit()
|
||||
})
|
||||
|
||||
describe("spawn tests", () => {
|
||||
let port: number
|
||||
let mockedCP: MockProxy<ChildProcess>
|
||||
let listeners: Record<string, (...args: any[]) => void>
|
||||
|
||||
beforeEach(async () => {
|
||||
port = await getFreePort()
|
||||
mockedCP = mock<ChildProcess>()
|
||||
listeners = {}
|
||||
|
||||
jest.spyOn(Kubectl.prototype, "checkBinary").mockReturnValueOnce(Promise.resolve(true))
|
||||
jest.spyOn(Kubectl.prototype, "ensureKubectl").mockReturnValueOnce(Promise.resolve(false))
|
||||
mockedCP.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): ChildProcess => {
|
||||
listeners[event] = listener
|
||||
return mockedCP
|
||||
})
|
||||
mockedCP.stderr = mock<Readable>()
|
||||
mockedCP.stderr.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
||||
listeners[`stderr/${event}`] = listener
|
||||
return mockedCP.stderr
|
||||
})
|
||||
mockedCP.stdout = mock<Readable>()
|
||||
mockedCP.stdout.on.mockImplementation((event: string, listener: (message: any, sendHandle: any) => void): Readable => {
|
||||
listeners[`stdout/${event}`] = listener
|
||||
return mockedCP.stdout
|
||||
})
|
||||
mockSpawn.mockImplementationOnce((command: string, args: readonly string[], options: SpawnOptions): ChildProcess => {
|
||||
expect(command).toBe(bundledKubectlPath())
|
||||
return mockedCP
|
||||
})
|
||||
mockWaitUntilUsed.mockReturnValueOnce(Promise.resolve())
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast errors", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
listeners["error"]({ message: "foobarbat" })
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "foobarbat", error: true }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast exit", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
listeners["exit"](0)
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "proxy exited with code: 0", error: false }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast errors from stderr", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
listeners["stderr/data"]("an error")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "an error", error: true }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast stdout serving info", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
listeners["stdout/data"]("Starting to serve on")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "Authentication proxy started\n" }] })
|
||||
})
|
||||
|
||||
it("should call spawn and broadcast stdout other info", async () => {
|
||||
const kap = new KubeAuthProxy(new Cluster({ id: "foobar", kubeConfigPath: "fake-path.yml" }), port, {})
|
||||
await kap.run()
|
||||
listeners["stdout/data"]("some info")
|
||||
|
||||
expect(mockBroadcastIpc).toBeCalledWith({ channel: "kube-auth:foobar", args: [{ data: "some info" }] })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
const logger = {
|
||||
silly: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
log: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
crit: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock("winston", () => ({
|
||||
format: {
|
||||
colorize: jest.fn(),
|
||||
combine: jest.fn(),
|
||||
simple: jest.fn(),
|
||||
label: jest.fn(),
|
||||
timestamp: jest.fn(),
|
||||
printf: jest.fn()
|
||||
},
|
||||
createLogger: jest.fn().mockReturnValue(logger),
|
||||
transports: {
|
||||
Console: jest.fn(),
|
||||
File: jest.fn(),
|
||||
}
|
||||
}))
|
||||
|
||||
import { KubeconfigManager } from "../kubeconfig-manager"
|
||||
import mockFs from "mock-fs"
|
||||
import { Cluster } from "../cluster";
|
||||
import { workspaceStore } from "../../common/workspace-store";
|
||||
import { ContextHandler } from "../context-handler";
|
||||
import { getFreePort } from "../port";
|
||||
import fse from "fs-extra"
|
||||
import { loadYaml } from "@kubernetes/client-node";
|
||||
import { Console } from "console";
|
||||
|
||||
console = new Console(process.stdout, process.stderr) // fix mockFS
|
||||
|
||||
describe("kubeconfig manager tests", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const mockOpts = {
|
||||
"minikube-config.yml": JSON.stringify({
|
||||
apiVersion: "v1",
|
||||
clusters: [{
|
||||
name: "minikube",
|
||||
cluster: {
|
||||
server: "https://192.168.64.3:8443",
|
||||
},
|
||||
}],
|
||||
contexts: [{
|
||||
context: {
|
||||
cluster: "minikube",
|
||||
user: "minikube",
|
||||
},
|
||||
name: "minikube",
|
||||
}],
|
||||
users: [{
|
||||
name: "minikube",
|
||||
}],
|
||||
kind: "Config",
|
||||
preferences: {},
|
||||
})
|
||||
}
|
||||
mockFs(mockOpts)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore()
|
||||
})
|
||||
|
||||
it("should create 'temp' kube config with proxy", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
workspace: workspaceStore.currentWorkspaceId
|
||||
})
|
||||
const contextHandler = new ContextHandler(cluster)
|
||||
const port = await getFreePort()
|
||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
|
||||
|
||||
expect(logger.error).not.toBeCalled()
|
||||
expect(kubeConfManager.getPath()).toBe("tmp/kubeconfig-foo")
|
||||
const file = await fse.readFile(kubeConfManager.getPath())
|
||||
const yml = loadYaml<any>(file.toString())
|
||||
expect(yml["current-context"]).toBe("minikube")
|
||||
expect(yml["clusters"][0]["cluster"]["server"]).toBe(`http://127.0.0.1:${port}/foo`)
|
||||
expect(yml["users"][0]["name"]).toBe("proxy")
|
||||
})
|
||||
|
||||
it("should remove 'temp' kube config on unlink and remove reference from inside class", async () => {
|
||||
const cluster = new Cluster({
|
||||
id: "foo",
|
||||
contextName: "minikube",
|
||||
kubeConfigPath: "minikube-config.yml",
|
||||
workspace: workspaceStore.currentWorkspaceId
|
||||
})
|
||||
const contextHandler = new ContextHandler(cluster)
|
||||
const port = await getFreePort()
|
||||
const kubeConfManager = await KubeconfigManager.create(cluster, contextHandler, port)
|
||||
|
||||
const configPath = kubeConfManager.getPath()
|
||||
expect(await fse.pathExists(configPath)).toBe(true)
|
||||
await kubeConfManager.unlink()
|
||||
expect(await fse.pathExists(configPath)).toBe(false)
|
||||
await kubeConfManager.unlink() // doesn't throw
|
||||
expect(kubeConfManager.getPath()).toBeUndefined()
|
||||
})
|
||||
})
|
||||
+10
-3
@@ -46,6 +46,7 @@ export class Cluster implements ClusterModel {
|
||||
public contextHandler: ContextHandler;
|
||||
protected kubeconfigManager: KubeconfigManager;
|
||||
protected eventDisposers: Function[] = [];
|
||||
protected activated = false;
|
||||
|
||||
whenInitialized = when(() => this.initialized);
|
||||
whenReady = when(() => this.ready);
|
||||
@@ -93,7 +94,7 @@ export class Cluster implements ClusterModel {
|
||||
async init(port: number) {
|
||||
try {
|
||||
this.contextHandler = new ContextHandler(this);
|
||||
this.kubeconfigManager = new KubeconfigManager(this, this.contextHandler, port);
|
||||
this.kubeconfigManager = await KubeconfigManager.create(this, this.contextHandler, port);
|
||||
this.kubeProxyUrl = `http://localhost:${port}${apiKubePrefix}`;
|
||||
this.initialized = true;
|
||||
logger.info(`[CLUSTER]: "${this.contextName}" init success`, {
|
||||
@@ -126,7 +127,10 @@ export class Cluster implements ClusterModel {
|
||||
}
|
||||
|
||||
@action
|
||||
async activate() {
|
||||
async activate(force = false ) {
|
||||
if (this.activated && !force) {
|
||||
return this.pushState();
|
||||
}
|
||||
logger.info(`[CLUSTER]: activate`, this.getMeta());
|
||||
await this.whenInitialized;
|
||||
if (!this.eventDisposers.length) {
|
||||
@@ -142,6 +146,7 @@ export class Cluster implements ClusterModel {
|
||||
this.kubeCtl = new Kubectl(this.version)
|
||||
this.kubeCtl.ensureKubectl() // download kubectl in background, so it's not blocking dashboard
|
||||
}
|
||||
this.activated = true
|
||||
return this.pushState();
|
||||
}
|
||||
|
||||
@@ -162,6 +167,7 @@ export class Cluster implements ClusterModel {
|
||||
this.online = false;
|
||||
this.accessible = false;
|
||||
this.ready = false;
|
||||
this.activated = false;
|
||||
this.pushState();
|
||||
}
|
||||
|
||||
@@ -184,6 +190,7 @@ export class Cluster implements ClusterModel {
|
||||
this.refreshEvents(),
|
||||
this.refreshAllowedResources(),
|
||||
]);
|
||||
this.ready = true
|
||||
}
|
||||
this.pushState();
|
||||
}
|
||||
@@ -234,7 +241,7 @@ export class Cluster implements ClusterModel {
|
||||
const apiUrl = this.kubeProxyUrl + path;
|
||||
return request(apiUrl, {
|
||||
json: true,
|
||||
timeout: 5000,
|
||||
timeout: 30000,
|
||||
...options,
|
||||
headers: {
|
||||
Host: `${this.id}.${new URL(this.kubeProxyUrl).host}`, // required in ClusterManager.getClusterForRequest()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ChildProcess, spawn } from "child_process"
|
||||
import { waitUntilUsed } from "tcp-port-used";
|
||||
import { broadcastIpc } from "../common/ipc";
|
||||
import type { Cluster } from "./cluster"
|
||||
import { bundledKubectl, Kubectl } from "./kubectl"
|
||||
import { Kubectl } from "./kubectl"
|
||||
import logger from "./logger"
|
||||
|
||||
export interface KubeAuthProxyLog {
|
||||
@@ -23,7 +23,7 @@ export class KubeAuthProxy {
|
||||
this.env = env
|
||||
this.port = port
|
||||
this.cluster = cluster
|
||||
this.kubectl = bundledKubectl
|
||||
this.kubectl = Kubectl.bundled()
|
||||
}
|
||||
|
||||
public async run(): Promise<void> {
|
||||
|
||||
@@ -11,8 +11,12 @@ export class KubeconfigManager {
|
||||
protected configDir = app.getPath("temp")
|
||||
protected tempFile: string;
|
||||
|
||||
constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) {
|
||||
this.init();
|
||||
private constructor(protected cluster: Cluster, protected contextHandler: ContextHandler, protected port: number) { }
|
||||
|
||||
static async create(cluster: Cluster, contextHandler: ContextHandler, port: number) {
|
||||
const kcm = new KubeconfigManager(cluster, contextHandler, port)
|
||||
await kcm.init()
|
||||
return kcm
|
||||
}
|
||||
|
||||
protected async init() {
|
||||
@@ -72,8 +76,13 @@ export class KubeconfigManager {
|
||||
return tempFile;
|
||||
}
|
||||
|
||||
unlink() {
|
||||
async unlink() {
|
||||
if (!this.tempFile) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.info('Deleting temporary kubeconfig: ' + this.tempFile)
|
||||
fs.unlinkSync(this.tempFile)
|
||||
await fs.unlink(this.tempFile)
|
||||
this.tempFile = undefined
|
||||
}
|
||||
}
|
||||
+19
-17
@@ -36,15 +36,21 @@ const packageMirrors: Map<string, string> = new Map([
|
||||
let bundledPath: string
|
||||
const initScriptVersionString = "# lens-initscript v3\n"
|
||||
|
||||
if (isDevelopment || isTestEnv) {
|
||||
const platformName = isWindows ? "windows" : process.platform
|
||||
bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl")
|
||||
} else {
|
||||
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
|
||||
}
|
||||
export function bundledKubectlPath(): string {
|
||||
if (bundledPath) { return bundledPath }
|
||||
|
||||
if (isWindows) {
|
||||
bundledPath = `${bundledPath}.exe`
|
||||
if (isDevelopment || isTestEnv) {
|
||||
const platformName = isWindows ? "windows" : process.platform
|
||||
bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl")
|
||||
} else {
|
||||
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
|
||||
}
|
||||
|
||||
if (isWindows) {
|
||||
bundledPath = `${bundledPath}.exe`
|
||||
}
|
||||
|
||||
return bundledPath
|
||||
}
|
||||
|
||||
export class Kubectl {
|
||||
@@ -58,7 +64,6 @@ export class Kubectl {
|
||||
return path.join((app || remote.app).getPath("userData"), "binaries", "kubectl")
|
||||
}
|
||||
|
||||
public static readonly bundledKubectlPath = bundledPath
|
||||
public static readonly bundledKubectlVersion: string = bundledVersion
|
||||
public static invalidBundle = false
|
||||
private static bundledInstance: Kubectl;
|
||||
@@ -102,7 +107,7 @@ export class Kubectl {
|
||||
}
|
||||
|
||||
public getBundledPath() {
|
||||
return Kubectl.bundledKubectlPath
|
||||
return bundledKubectlPath()
|
||||
}
|
||||
|
||||
public getPathFromPreferences() {
|
||||
@@ -125,19 +130,19 @@ export class Kubectl {
|
||||
// return binary name if bundled path is not functional
|
||||
if (!await this.checkBinary(this.getBundledPath(), false)) {
|
||||
Kubectl.invalidBundle = true
|
||||
return path.basename(bundledPath)
|
||||
return path.basename(this.getBundledPath())
|
||||
}
|
||||
|
||||
try {
|
||||
if (!await this.ensureKubectl()) {
|
||||
logger.error("Failed to ensure kubectl, fallback to the bundled version")
|
||||
return Kubectl.bundledKubectlPath
|
||||
return this.getBundledPath()
|
||||
}
|
||||
return this.path
|
||||
} catch (err) {
|
||||
logger.error("Failed to ensure kubectl, fallback to the bundled version")
|
||||
logger.error(err)
|
||||
return Kubectl.bundledKubectlPath
|
||||
return this.getBundledPath()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +188,7 @@ export class Kubectl {
|
||||
try {
|
||||
const exist = await pathExists(this.path)
|
||||
if (!exist) {
|
||||
await fs.promises.copyFile(Kubectl.bundledKubectlPath, this.path)
|
||||
await fs.promises.copyFile(this.getBundledPath(), this.path)
|
||||
await fs.promises.chmod(this.path, 0o755)
|
||||
}
|
||||
return true
|
||||
@@ -332,6 +337,3 @@ export class Kubectl {
|
||||
return packageMirrors.get("default") // MacOS packages are only available from default
|
||||
}
|
||||
}
|
||||
|
||||
const bundledKubectl = Kubectl.bundled()
|
||||
export { bundledKubectl }
|
||||
@@ -1,20 +1,20 @@
|
||||
import packageInfo from "../../package.json"
|
||||
import path from "path"
|
||||
import { bundledKubectl, Kubectl } from "../../src/main/kubectl";
|
||||
import { Kubectl } from "../../src/main/kubectl";
|
||||
import { isWindows } from "../common/vars";
|
||||
|
||||
jest.mock("../common/user-store");
|
||||
|
||||
describe("kubectlVersion", () => {
|
||||
it("returns bundled version if exactly same version used", async () => {
|
||||
const kubectl = new Kubectl(bundledKubectl.kubectlVersion)
|
||||
expect(kubectl.kubectlVersion).toBe(bundledKubectl.kubectlVersion)
|
||||
const kubectl = new Kubectl(Kubectl.bundled().kubectlVersion)
|
||||
expect(kubectl.kubectlVersion).toBe(Kubectl.bundled().kubectlVersion)
|
||||
})
|
||||
|
||||
it("returns bundled version if same major.minor version is used", async () => {
|
||||
const { bundledKubectlVersion } = packageInfo.config;
|
||||
const kubectl = new Kubectl(bundledKubectlVersion);
|
||||
expect(kubectl.kubectlVersion).toBe(bundledKubectl.kubectlVersion)
|
||||
expect(kubectl.kubectlVersion).toBe(Kubectl.bundled().kubectlVersion)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LensApiRequest } from "../router"
|
||||
import { LensApi } from "../lens-api"
|
||||
import { spawn, ChildProcessWithoutNullStreams } from "child_process"
|
||||
import { bundledKubectl } from "../kubectl"
|
||||
import { Kubectl } from "../kubectl"
|
||||
import { getFreePort } from "../port"
|
||||
import { shell } from "electron"
|
||||
import * as tcpPortUsed from "tcp-port-used"
|
||||
@@ -37,7 +37,7 @@ class PortForward {
|
||||
|
||||
public async start() {
|
||||
this.localPort = await getFreePort()
|
||||
const kubectlBin = await bundledKubectl.getPath()
|
||||
const kubectlBin = await Kubectl.bundled().getPath()
|
||||
const args = [
|
||||
"--kubeconfig", this.kubeConfig,
|
||||
"port-forward",
|
||||
|
||||
@@ -38,7 +38,7 @@ export class ShellSession extends EventEmitter {
|
||||
|
||||
public async open() {
|
||||
this.kubectlBinDir = await this.kubectl.binDir()
|
||||
const pathFromPreferences = userStore.preferences.kubectlBinariesPath || Kubectl.bundledKubectlPath
|
||||
const pathFromPreferences = userStore.preferences.kubectlBinariesPath || this.kubectl.getBundledPath()
|
||||
this.kubectlPathDir = userStore.preferences.downloadKubectlBinaries ? this.kubectlBinDir : path.dirname(pathFromPreferences)
|
||||
this.helmBinDir = helmCli.getBinaryDir()
|
||||
const env = await this.getCachedShellEnv()
|
||||
|
||||
Reference in new issue
Block a user