diff --git a/Makefile b/Makefile index 068116b3ba..54dcd6fc94 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ endif lint: yarn lint -test: +test: download-bins yarn test integration-linux: diff --git a/src/main/kube-auth-proxy.ts b/src/main/kube-auth-proxy.ts index b388720cce..33521fdcf5 100644 --- a/src/main/kube-auth-proxy.ts +++ b/src/main/kube-auth-proxy.ts @@ -44,6 +44,10 @@ export class KubeAuthProxy { } logger.debug(`spawning kubectl proxy with args: ${args}`) this.proxyProcess = spawn(proxyBin, args, { env: this.env, }) + this.proxyProcess.on("error", (error) => { + this.sendIpcLogMessage({ data: error.message, error: true }) + this.exit() + }) this.proxyProcess.on("exit", (code) => { this.sendIpcLogMessage({ data: `proxy exited with code: ${code}`, error: code > 0 }) diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index 46e1c4ce24..ae18beb67a 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -9,7 +9,7 @@ import { helmCli } from "./helm/helm-cli" import { userStore } from "../common/user-store" import { customRequest } from "../common/request"; import { getBundledKubectlVersion } from "../common/utils/app-version" -import { isDevelopment, isWindows } from "../common/vars"; +import { isDevelopment, isWindows, isTestEnv } from "../common/vars"; const bundledVersion = getBundledKubectlVersion() const kubectlMap: Map = new Map([ @@ -35,8 +35,9 @@ const packageMirrors: Map = new Map([ let bundledPath: string const initScriptVersionString = "# lens-initscript v3\n" -if (isDevelopment) { - bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl") +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") } @@ -58,6 +59,7 @@ export class Kubectl { public static readonly bundledKubectlPath = bundledPath public static readonly bundledKubectlVersion: string = bundledVersion + public static invalidBundle = false private static bundledInstance: Kubectl; // Returns the single bundled Kubectl instance @@ -98,9 +100,22 @@ export class Kubectl { this.path = path.join(this.dirname, binaryName) } - public async getPath(): Promise { + public getBundledPath() { + return Kubectl.bundledKubectlPath + } + + public async getPath(bundled = false): Promise { + // return binary name if bundled path is not functional + if (!await this.checkBinary(this.getBundledPath(), false)) { + Kubectl.invalidBundle = true + return path.basename(bundledPath) + } + try { - await this.ensureKubectl() + if (!await this.ensureKubectl()) { + logger.error("Failed to ensure kubectl, fallback to the bundled version") + return Kubectl.bundledKubectlPath + } return this.path } catch (err) { logger.error("Failed to ensure kubectl, fallback to the bundled version") @@ -119,16 +134,15 @@ export class Kubectl { } } - public async checkBinary(checkVersion = true) { - const exists = await pathExists(this.path) + public async checkBinary(path: string, checkVersion = true) { + const exists = await pathExists(path) if (exists) { - if (!checkVersion) { - return true - } - try { - const { stdout } = await promiseExec(`"${this.path}" version --client=true -o json`) + const { stdout } = await promiseExec(`"${path}" version --client=true -o json`) const output = JSON.parse(stdout) + if (!checkVersion) { + return true + } let version: string = output.clientVersion.gitVersion if (version[0] === 'v') { version = version.slice(1) @@ -165,15 +179,28 @@ export class Kubectl { } public async ensureKubectl(): Promise { + if (Kubectl.invalidBundle) { + logger.error(`Detected invalid bundle binary, returning ...`) + return false + } await ensureDir(this.dirname, 0o755) return lockFile.lock(this.dirname).then(async (release) => { logger.debug(`Acquired a lock for ${this.kubectlVersion}`) const bundled = await this.checkBundled() - const isValid = await this.checkBinary(!bundled) - if (!isValid) { + let isValid = await this.checkBinary(this.path, !bundled) + if (!isValid && !bundled) { await this.downloadKubectl().catch((error) => { logger.error(error) + logger.debug(`Releasing lock for ${this.kubectlVersion}`) + release() + return false }); + isValid = !await this.checkBinary(this.path, false) + } + if(!isValid) { + logger.debug(`Releasing lock for ${this.kubectlVersion}`) + release() + return false } await this.writeInitScripts().catch((error) => { logger.error("Failed to write init scripts"); diff --git a/src/main/kubectl_spec.ts b/src/main/kubectl_spec.ts index 005361dfa3..4e5cdbf986 100644 --- a/src/main/kubectl_spec.ts +++ b/src/main/kubectl_spec.ts @@ -1,5 +1,7 @@ import packageInfo from "../../package.json" +import path from "path" import { bundledKubectl, Kubectl } from "../../src/main/kubectl"; +import { isWindows } from "../common/vars"; jest.mock("../common/user-store"); @@ -15,3 +17,29 @@ describe("kubectlVersion", () => { expect(kubectl.kubectlVersion).toBe(bundledKubectl.kubectlVersion) }) }) + +describe("getPath()", () => { + it("returns path to downloaded kubectl binary", async () => { + const { bundledKubectlVersion } = packageInfo.config; + const kubectl = new Kubectl(bundledKubectlVersion); + const kubectlPath = await kubectl.getPath() + let binaryName = "kubectl" + if (isWindows) { + binaryName += ".exe" + } + const expectedPath = path.join(Kubectl.kubectlDir, Kubectl.bundledKubectlVersion, binaryName) + expect(kubectlPath).toBe(expectedPath) + }) + + it("returns plain binary name if bundled kubectl is non-functional", async () => { + const { bundledKubectlVersion } = packageInfo.config; + const kubectl = new Kubectl(bundledKubectlVersion); + jest.spyOn(kubectl, "getBundledPath").mockReturnValue("/invalid/path/kubectl") + const kubectlPath = await kubectl.getPath() + let binaryName = "kubectl" + if (isWindows) { + binaryName += ".exe" + } + expect(kubectlPath).toBe(binaryName) + }) +})