1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Detect invalid bundled kubectl (#778)

* Test bundled kubectl

Signed-off-by: Lauri Nevala <lauri.nevala@gmail.com>
This commit is contained in:
Lauri Nevala 2020-09-01 14:28:59 +03:00 committed by GitHub
parent f4d262b294
commit 533646cba5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 15 deletions

View File

@ -28,7 +28,7 @@ endif
lint: lint:
yarn lint yarn lint
test: test: download-bins
yarn test yarn test
integration-linux: integration-linux:

View File

@ -44,6 +44,10 @@ export class KubeAuthProxy {
} }
logger.debug(`spawning kubectl proxy with args: ${args}`) logger.debug(`spawning kubectl proxy with args: ${args}`)
this.proxyProcess = spawn(proxyBin, args, { env: this.env, }) 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.proxyProcess.on("exit", (code) => {
this.sendIpcLogMessage({ data: `proxy exited with code: ${code}`, error: code > 0 }) this.sendIpcLogMessage({ data: `proxy exited with code: ${code}`, error: code > 0 })

View File

@ -9,7 +9,7 @@ import { helmCli } from "./helm/helm-cli"
import { userStore } from "../common/user-store" import { userStore } from "../common/user-store"
import { customRequest } from "../common/request"; import { customRequest } from "../common/request";
import { getBundledKubectlVersion } from "../common/utils/app-version" import { getBundledKubectlVersion } from "../common/utils/app-version"
import { isDevelopment, isWindows } from "../common/vars"; import { isDevelopment, isWindows, isTestEnv } from "../common/vars";
const bundledVersion = getBundledKubectlVersion() const bundledVersion = getBundledKubectlVersion()
const kubectlMap: Map<string, string> = new Map([ const kubectlMap: Map<string, string> = new Map([
@ -35,8 +35,9 @@ const packageMirrors: Map<string, string> = new Map([
let bundledPath: string let bundledPath: string
const initScriptVersionString = "# lens-initscript v3\n" const initScriptVersionString = "# lens-initscript v3\n"
if (isDevelopment) { if (isDevelopment || isTestEnv) {
bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl") const platformName = isWindows ? "windows" : process.platform
bundledPath = path.join(process.cwd(), "binaries", "client", platformName, process.arch, "kubectl")
} else { } else {
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl") bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
} }
@ -58,6 +59,7 @@ export class Kubectl {
public static readonly bundledKubectlPath = bundledPath public static readonly bundledKubectlPath = bundledPath
public static readonly bundledKubectlVersion: string = bundledVersion public static readonly bundledKubectlVersion: string = bundledVersion
public static invalidBundle = false
private static bundledInstance: Kubectl; private static bundledInstance: Kubectl;
// Returns the single bundled Kubectl instance // Returns the single bundled Kubectl instance
@ -98,9 +100,22 @@ export class Kubectl {
this.path = path.join(this.dirname, binaryName) this.path = path.join(this.dirname, binaryName)
} }
public async getPath(): Promise<string> { public getBundledPath() {
return Kubectl.bundledKubectlPath
}
public async getPath(bundled = false): Promise<string> {
// return binary name if bundled path is not functional
if (!await this.checkBinary(this.getBundledPath(), false)) {
Kubectl.invalidBundle = true
return path.basename(bundledPath)
}
try { 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 return this.path
} catch (err) { } catch (err) {
logger.error("Failed to ensure kubectl, fallback to the bundled version") logger.error("Failed to ensure kubectl, fallback to the bundled version")
@ -119,16 +134,15 @@ export class Kubectl {
} }
} }
public async checkBinary(checkVersion = true) { public async checkBinary(path: string, checkVersion = true) {
const exists = await pathExists(this.path) const exists = await pathExists(path)
if (exists) { if (exists) {
try {
const { stdout } = await promiseExec(`"${path}" version --client=true -o json`)
const output = JSON.parse(stdout)
if (!checkVersion) { if (!checkVersion) {
return true return true
} }
try {
const { stdout } = await promiseExec(`"${this.path}" version --client=true -o json`)
const output = JSON.parse(stdout)
let version: string = output.clientVersion.gitVersion let version: string = output.clientVersion.gitVersion
if (version[0] === 'v') { if (version[0] === 'v') {
version = version.slice(1) version = version.slice(1)
@ -165,15 +179,28 @@ export class Kubectl {
} }
public async ensureKubectl(): Promise<boolean> { public async ensureKubectl(): Promise<boolean> {
if (Kubectl.invalidBundle) {
logger.error(`Detected invalid bundle binary, returning ...`)
return false
}
await ensureDir(this.dirname, 0o755) await ensureDir(this.dirname, 0o755)
return lockFile.lock(this.dirname).then(async (release) => { return lockFile.lock(this.dirname).then(async (release) => {
logger.debug(`Acquired a lock for ${this.kubectlVersion}`) logger.debug(`Acquired a lock for ${this.kubectlVersion}`)
const bundled = await this.checkBundled() const bundled = await this.checkBundled()
const isValid = await this.checkBinary(!bundled) let isValid = await this.checkBinary(this.path, !bundled)
if (!isValid) { if (!isValid && !bundled) {
await this.downloadKubectl().catch((error) => { await this.downloadKubectl().catch((error) => {
logger.error(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) => { await this.writeInitScripts().catch((error) => {
logger.error("Failed to write init scripts"); logger.error("Failed to write init scripts");

View File

@ -1,5 +1,7 @@
import packageInfo from "../../package.json" import packageInfo from "../../package.json"
import path from "path"
import { bundledKubectl, Kubectl } from "../../src/main/kubectl"; import { bundledKubectl, Kubectl } from "../../src/main/kubectl";
import { isWindows } from "../common/vars";
jest.mock("../common/user-store"); jest.mock("../common/user-store");
@ -15,3 +17,29 @@ describe("kubectlVersion", () => {
expect(kubectl.kubectlVersion).toBe(bundledKubectl.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)
})
})