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

Merge remote-tracking branch 'origin/master' into lens_restructure

# Conflicts:
#	src/main/kubectl.ts
#	src/main/shell-sync.ts
#	src/renderer/api/__test__/parseAPI.test.ts
#	src/renderer/api/kube-api.ts
This commit is contained in:
Roman 2020-06-23 18:17:17 +03:00
commit 468847c7cb
5 changed files with 96 additions and 101 deletions

View File

@ -38,7 +38,7 @@ const vmURL = formatUrl({
})
async function main() {
await shellSync()
shellSync(app.getLocale())
const updater = new AppUpdater()
updater.start();

View File

@ -1,19 +1,16 @@
import packageInfo from "../../package.json"
import { app, remote } from "electron"
import path from "path"
import fs from "fs"
import request from "request"
import requestPromise from "request-promise-native"
import { promiseExec} from "./promise-exec"
import logger from "./logger"
import { ensureDir, pathExists } from "fs-extra"
import md5File from "md5-file"
import { globalRequestOpts } from "../common/request"
import lockFile from "proper-lockfile"
import * as lockFile from "proper-lockfile"
import { helmCli } from "./helm-cli"
import { userStore } from "../common/user-store"
import { isDevelopment, isMac, isWindows } from "../common/vars";
const bundledVersion = packageInfo.config.bundledKubectlVersion;
const bundledVersion = require("../../package.json").config.bundledKubectlVersion
const kubectlMap: Map<string, string> = new Map([
["1.7", "1.8.15"],
["1.8", "1.9.10"],
@ -36,18 +33,16 @@ const packageMirrors: Map<string, string> = new Map([
const initScriptVersionString = "# lens-initscript v3\n"
const isDevelopment = process.env.NODE_ENV !== "production"
let bundledPath: string = null
if (isDevelopment) {
if(isDevelopment) {
bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl")
}
else {
} else {
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
}
if (isWindows) {
bundledPath = `${bundledPath}.exe`
}
if(process.platform === "win32") bundledPath = `${bundledPath}.exe`
export class Kubectl {
@ -59,12 +54,12 @@ export class Kubectl {
public static readonly kubectlDir = path.join((app || remote.app).getPath("userData"), "binaries", "kubectl")
public static readonly bundledKubectlPath = bundledPath
public static readonly bundledKubectlVersion = bundledVersion
public static readonly bundledKubectlVersion: string = bundledVersion
private static bundledInstance: Kubectl;
// Returns the single bundled Kubectl instance
public static bundled() {
if (!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion)
if(!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion)
return Kubectl.bundledInstance
}
@ -73,29 +68,26 @@ export class Kubectl {
const minorVersion = versionParts[1]
/* minorVersion is the first two digits of kube server version
if the version map includes that, use that version, if not, fallback to the exact x.y.z of kube version */
if (kubectlMap.has(minorVersion)) {
if(kubectlMap.has(minorVersion)) {
this.kubectlVersion = kubectlMap.get(minorVersion)
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using version map")
}
else {
} else {
this.kubectlVersion = versionParts[1] + versionParts[2]
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using fallback")
}
let arch = null
if (process.arch == "x64") {
if(process.arch == "x64") {
arch = "amd64"
}
else if (process.arch == "x86" || process.arch == "ia32") {
} else if(process.arch == "x86" || process.arch == "ia32") {
arch = "386"
}
else {
} else {
arch = process.arch
}
const platformName = isWindows ? "windows" : process.platform
const binaryName = isWindows ? "kubectl.exe" : "kubectl"
const platformName = process.platform === "win32" ? "windows" : process.platform
const binaryName = process.platform === "win32" ? "kubectl.exe" : "kubectl"
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}`
@ -107,7 +99,7 @@ export class Kubectl {
try {
await this.ensureKubectl()
return this.path
} catch (err) {
} catch(err) {
logger.error("Failed to ensure kubectl, fallback to the bundled version")
logger.error(err)
return Kubectl.bundledKubectlPath
@ -118,55 +110,42 @@ export class Kubectl {
try {
await this.ensureKubectl()
return this.dirname
} catch (err) {
} catch(err) {
logger.error(err)
return ""
}
}
protected async urlEtag() {
const response = await requestPromise({
method: "HEAD",
uri: this.url,
resolveWithFullResponse: true,
timeout: 4000,
...this.getRequestOpts()
}).catch((error) => {
logger.error(error)
})
if (response && response.headers["etag"]) {
return response.headers["etag"].replace(/"/g, "")
}
return ""
}
public async checkBinary(checkTag = true) {
public async checkBinary(checkVersion = true) {
const exists = await pathExists(this.path)
if (exists) {
if (!checkTag) {
return true
}
const hash = md5File.sync(this.path)
const etag = await this.urlEtag()
if (etag === "") {
logger.debug("Cannot resolve kubectl remote etag")
return true
}
if (hash == etag) {
logger.debug("Kubectl md5sum matches the remote etag")
if (!checkVersion) {
return true
}
logger.error("Kubectl md5sum " + hash + " does not match the remote etag " + etag + ", unlinking and downloading again")
try {
const { stdout, stderr } = await promiseExec(`"${this.path}" version --client=true -o json`)
const output = JSON.parse(stdout)
let version: string = output.clientVersion.gitVersion
if (version[0] === 'v') {
version = version.slice(1)
}
if (version === this.kubectlVersion) {
logger.debug(`Local kubectl is version ${this.kubectlVersion}`)
return true
}
logger.error(`Local kubectl is version ${version}, expected ${this.kubectlVersion}, unlinking`)
}
catch(err) {
logger.error(`Local kubectl failed to run properly (${err.message}), unlinking`)
}
await fs.promises.unlink(this.path)
}
return false
}
protected async checkBundled(): Promise<boolean> {
if (this.kubectlVersion === Kubectl.bundledKubectlVersion) {
if(this.kubectlVersion === Kubectl.bundledKubectlVersion) {
try {
const exist = await pathExists(this.path)
if (!exist) {
@ -174,12 +153,11 @@ export class Kubectl {
await fs.promises.chmod(this.path, 0o755)
}
return true
} catch (err) {
} catch(err) {
logger.error("Could not copy the bundled kubectl to app-data: " + err)
return false
}
}
else {
} else {
return false
}
}
@ -190,15 +168,10 @@ export class Kubectl {
logger.debug(`Acquired a lock for ${this.kubectlVersion}`)
const bundled = await this.checkBundled()
const isValid = await this.checkBinary(!bundled)
if (!isValid) {
await this.downloadKubectl().catch((error) => {
logger.error(error)
});
if(!isValid) {
await this.downloadKubectl().catch((error) => { logger.error(error) });
}
await this.writeInitScripts().catch((error) => {
logger.error("Failed to write init scripts");
logger.error(error)
})
await this.writeInitScripts().catch((error) => { logger.error("Failed to write init scripts"); logger.error(error) })
logger.debug(`Releasing lock for ${this.kubectlVersion}`)
release()
return true
@ -221,19 +194,16 @@ export class Kubectl {
const file = fs.createWriteStream(this.path)
stream.on("complete", () => {
logger.debug("kubectl binary download finished")
file.end(() => {
})
file.end(() => {})
})
stream.on("error", (error) => {
logger.error(error)
fs.unlink(this.path, () => {
})
fs.unlink(this.path, () => {})
reject(error)
})
file.on("close", () => {
logger.debug("kubectl binary download closed")
fs.chmod(this.path, 0o755, () => {
})
fs.chmod(this.path, 0o755, () => {})
resolve()
})
stream.pipe(file)
@ -242,7 +212,7 @@ export class Kubectl {
protected async scriptIsLatest(scriptPath: string) {
const scriptExists = await pathExists(scriptPath)
if (!scriptExists) return false
if(!scriptExists) return false
try {
const filehandle = await fs.promises.open(scriptPath, 'r')
@ -261,7 +231,7 @@ export class Kubectl {
const fsPromises = fs.promises;
const bashScriptPath = path.join(this.dirname, '.bash_set_path')
const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath)
if (!bashScriptIsLatest) {
if(!bashScriptIsLatest) {
let bashScript = "" + initScriptVersionString
bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n"
@ -280,7 +250,7 @@ export class Kubectl {
const zshScriptPath = path.join(this.dirname, '.zlogin')
const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath)
if (!zshScriptIsLatest) {
if(!zshScriptIsLatest) {
let zshScript = "" + initScriptVersionString
zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
@ -314,13 +284,11 @@ export class Kubectl {
}
protected getDownloadMirror() {
if (isMac) {
if (process.platform == "darwin") {
return packageMirrors.get("default") // MacOS packages are only available from default
}
const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror)
if (mirror) {
return mirror
}
if (mirror) { return mirror }
return packageMirrors.get("default")
}

View File

@ -1,19 +1,34 @@
import shellEnv from "shell-env"
import logger from "./logger"
import { isMac, isProduction } from "../common/vars";
import os from "os";
export async function shellSync() {
const env = await shellEnv()
interface Env {
[key: string]: string;
}
/**
* shellSync loads what would have been the environment if this application was
* run from the command line, into the process.env object. This is especially
* useful on macos where this always needs to be done.
* @param locale Should be electron's `app.getLocale()`
*/
export function shellSync(locale: string) {
const { shell } = os.userInfo();
const env: Env = JSON.parse(JSON.stringify(shellEnv.sync(shell)))
if (!env.LANG) {
// the LANG env var expects an underscore instead of electron's dash
env.LANG = `${locale.replace('-', '_')}.UTF-8`;
} else if (!env.LANG.endsWith(".UTF-8")) {
env.LANG += ".UTF-8"
}
// Overwrite PATH on darwin
if (isProduction && isMac) {
if (process.env.NODE_ENV === "production" && process.platform === "darwin") {
process.env["PATH"] = env.PATH
}
let key = null
for (key in env) {
if (!env.hasOwnProperty(key) || process.env[key]) continue // skip existing and prototype keys
logger.debug("Imported " + key + " from login shell to process environment")
process.env[key] = env[key]
}
// The spread operator allows joining of objects. The precedence is last to first.
process.env = {
...env,
...process.env,
};
}

View File

@ -2,7 +2,7 @@ import { IKubeApiLinkBase, KubeApi } from "../kube-api";
interface ParseAPITest {
url: string;
expected: IKubeApiLinkBase;
expected: Required<IKubeApiLinkBase>;
}
const tests: ParseAPITest[] = [
@ -97,6 +97,19 @@ const tests: ParseAPITest[] = [
namespace: undefined,
},
},
{
url: "/api/v1/namespaces/kube-public",
expected: {
apiBase: "/api/v1/namespaces",
apiPrefix: "/api",
apiGroup: "",
apiVersion: "v1",
apiVersionWithGroup: "v1",
resource: "namespaces",
name: "kube-public",
namespace: undefined,
},
},
];
jest.mock('../kube-watch-api.ts', () => 'KubeWatchApi');

View File

@ -42,7 +42,6 @@ export interface IKubeApiLinkBase extends IKubeApiLinkRef {
export class KubeApi<T extends KubeObject = any> {
static parseApi(apiPath = ""): IKubeApiLinkBase {
apiPath = new URL(apiPath, location.origin).pathname;
const [, prefix, ...parts] = apiPath.split("/");
const apiPrefix = `/${prefix}`;
@ -51,11 +50,11 @@ export class KubeApi<T extends KubeObject = any> {
if (namespaced) {
switch (right.length) {
case 0:
resource = "namespaces"; // special case this due to `split` removing namespaces
break;
case 1:
resource = right[0];
name = right[0];
// fallthroughcase 0:
resource = "namespaces"; // special case this due to `split` removing namespaces
break;
default:
[namespace, resource, name] = right;
@ -68,7 +67,7 @@ export class KubeApi<T extends KubeObject = any> {
switch (left.length) {
case 2:
resource = left.pop();
case 1:
// fallthroughcase 1:
apiVersion = left.pop();
apiGroup = "";
break;