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:
commit
468847c7cb
@ -38,7 +38,7 @@ const vmURL = formatUrl({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await shellSync()
|
shellSync(app.getLocale())
|
||||||
|
|
||||||
const updater = new AppUpdater()
|
const updater = new AppUpdater()
|
||||||
updater.start();
|
updater.start();
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
import packageInfo from "../../package.json"
|
|
||||||
import { app, remote } from "electron"
|
import { app, remote } from "electron"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import request from "request"
|
import request from "request"
|
||||||
import requestPromise from "request-promise-native"
|
import { promiseExec} from "./promise-exec"
|
||||||
import logger from "./logger"
|
import logger from "./logger"
|
||||||
import { ensureDir, pathExists } from "fs-extra"
|
import { ensureDir, pathExists } from "fs-extra"
|
||||||
import md5File from "md5-file"
|
|
||||||
import { globalRequestOpts } from "../common/request"
|
import { globalRequestOpts } from "../common/request"
|
||||||
import lockFile from "proper-lockfile"
|
import * as lockFile from "proper-lockfile"
|
||||||
import { helmCli } from "./helm-cli"
|
import { helmCli } from "./helm-cli"
|
||||||
import { userStore } from "../common/user-store"
|
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([
|
const kubectlMap: Map<string, string> = new Map([
|
||||||
["1.7", "1.8.15"],
|
["1.7", "1.8.15"],
|
||||||
["1.8", "1.9.10"],
|
["1.8", "1.9.10"],
|
||||||
@ -36,18 +33,16 @@ const packageMirrors: Map<string, string> = new Map([
|
|||||||
|
|
||||||
const initScriptVersionString = "# lens-initscript v3\n"
|
const initScriptVersionString = "# lens-initscript v3\n"
|
||||||
|
|
||||||
|
const isDevelopment = process.env.NODE_ENV !== "production"
|
||||||
let bundledPath: string = null
|
let bundledPath: string = null
|
||||||
|
|
||||||
if (isDevelopment) {
|
if(isDevelopment) {
|
||||||
bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl")
|
bundledPath = path.join(process.cwd(), "binaries", "client", process.platform, process.arch, "kubectl")
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
|
bundledPath = path.join(process.resourcesPath, process.arch, "kubectl")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isWindows) {
|
if(process.platform === "win32") bundledPath = `${bundledPath}.exe`
|
||||||
bundledPath = `${bundledPath}.exe`
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Kubectl {
|
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 kubectlDir = path.join((app || remote.app).getPath("userData"), "binaries", "kubectl")
|
||||||
public static readonly bundledKubectlPath = bundledPath
|
public static readonly bundledKubectlPath = bundledPath
|
||||||
public static readonly bundledKubectlVersion = bundledVersion
|
public static readonly bundledKubectlVersion: string = bundledVersion
|
||||||
private static bundledInstance: Kubectl;
|
private static bundledInstance: Kubectl;
|
||||||
|
|
||||||
// Returns the single bundled Kubectl instance
|
// Returns the single bundled Kubectl instance
|
||||||
public static bundled() {
|
public static bundled() {
|
||||||
if (!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion)
|
if(!Kubectl.bundledInstance) Kubectl.bundledInstance = new Kubectl(Kubectl.bundledKubectlVersion)
|
||||||
return Kubectl.bundledInstance
|
return Kubectl.bundledInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,29 +68,26 @@ export class Kubectl {
|
|||||||
const minorVersion = versionParts[1]
|
const minorVersion = versionParts[1]
|
||||||
/* minorVersion is the first two digits of kube server version
|
/* 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 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)
|
this.kubectlVersion = kubectlMap.get(minorVersion)
|
||||||
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using version map")
|
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using version map")
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
this.kubectlVersion = versionParts[1] + versionParts[2]
|
this.kubectlVersion = versionParts[1] + versionParts[2]
|
||||||
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using fallback")
|
logger.debug("Set kubectl version " + this.kubectlVersion + " for cluster version " + clusterVersion + " using fallback")
|
||||||
}
|
}
|
||||||
|
|
||||||
let arch = null
|
let arch = null
|
||||||
|
|
||||||
if (process.arch == "x64") {
|
if(process.arch == "x64") {
|
||||||
arch = "amd64"
|
arch = "amd64"
|
||||||
}
|
} else if(process.arch == "x86" || process.arch == "ia32") {
|
||||||
else if (process.arch == "x86" || process.arch == "ia32") {
|
|
||||||
arch = "386"
|
arch = "386"
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
arch = process.arch
|
arch = process.arch
|
||||||
}
|
}
|
||||||
|
|
||||||
const platformName = isWindows ? "windows" : process.platform
|
const platformName = process.platform === "win32" ? "windows" : process.platform
|
||||||
const binaryName = isWindows ? "kubectl.exe" : "kubectl"
|
const binaryName = process.platform === "win32" ? "kubectl.exe" : "kubectl"
|
||||||
|
|
||||||
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}`
|
this.url = `${this.getDownloadMirror()}/v${this.kubectlVersion}/bin/${platformName}/${arch}/${binaryName}`
|
||||||
|
|
||||||
@ -107,7 +99,7 @@ export class Kubectl {
|
|||||||
try {
|
try {
|
||||||
await this.ensureKubectl()
|
await this.ensureKubectl()
|
||||||
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")
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return Kubectl.bundledKubectlPath
|
return Kubectl.bundledKubectlPath
|
||||||
@ -118,55 +110,42 @@ export class Kubectl {
|
|||||||
try {
|
try {
|
||||||
await this.ensureKubectl()
|
await this.ensureKubectl()
|
||||||
return this.dirname
|
return this.dirname
|
||||||
} catch (err) {
|
} catch(err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async urlEtag() {
|
public async checkBinary(checkVersion = true) {
|
||||||
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) {
|
|
||||||
const exists = await pathExists(this.path)
|
const exists = await pathExists(this.path)
|
||||||
if (exists) {
|
if (exists) {
|
||||||
if (!checkTag) {
|
if (!checkVersion) {
|
||||||
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")
|
|
||||||
return true
|
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)
|
await fs.promises.unlink(this.path)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async checkBundled(): Promise<boolean> {
|
protected async checkBundled(): Promise<boolean> {
|
||||||
if (this.kubectlVersion === Kubectl.bundledKubectlVersion) {
|
if(this.kubectlVersion === Kubectl.bundledKubectlVersion) {
|
||||||
try {
|
try {
|
||||||
const exist = await pathExists(this.path)
|
const exist = await pathExists(this.path)
|
||||||
if (!exist) {
|
if (!exist) {
|
||||||
@ -174,12 +153,11 @@ export class Kubectl {
|
|||||||
await fs.promises.chmod(this.path, 0o755)
|
await fs.promises.chmod(this.path, 0o755)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch(err) {
|
||||||
logger.error("Could not copy the bundled kubectl to app-data: " + err)
|
logger.error("Could not copy the bundled kubectl to app-data: " + err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,15 +168,10 @@ export class Kubectl {
|
|||||||
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)
|
const isValid = await this.checkBinary(!bundled)
|
||||||
if (!isValid) {
|
if(!isValid) {
|
||||||
await this.downloadKubectl().catch((error) => {
|
await this.downloadKubectl().catch((error) => { logger.error(error) });
|
||||||
logger.error(error)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
await this.writeInitScripts().catch((error) => {
|
await this.writeInitScripts().catch((error) => { logger.error("Failed to write init scripts"); logger.error(error) })
|
||||||
logger.error("Failed to write init scripts");
|
|
||||||
logger.error(error)
|
|
||||||
})
|
|
||||||
logger.debug(`Releasing lock for ${this.kubectlVersion}`)
|
logger.debug(`Releasing lock for ${this.kubectlVersion}`)
|
||||||
release()
|
release()
|
||||||
return true
|
return true
|
||||||
@ -221,19 +194,16 @@ export class Kubectl {
|
|||||||
const file = fs.createWriteStream(this.path)
|
const file = fs.createWriteStream(this.path)
|
||||||
stream.on("complete", () => {
|
stream.on("complete", () => {
|
||||||
logger.debug("kubectl binary download finished")
|
logger.debug("kubectl binary download finished")
|
||||||
file.end(() => {
|
file.end(() => {})
|
||||||
})
|
|
||||||
})
|
})
|
||||||
stream.on("error", (error) => {
|
stream.on("error", (error) => {
|
||||||
logger.error(error)
|
logger.error(error)
|
||||||
fs.unlink(this.path, () => {
|
fs.unlink(this.path, () => {})
|
||||||
})
|
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
file.on("close", () => {
|
file.on("close", () => {
|
||||||
logger.debug("kubectl binary download closed")
|
logger.debug("kubectl binary download closed")
|
||||||
fs.chmod(this.path, 0o755, () => {
|
fs.chmod(this.path, 0o755, () => {})
|
||||||
})
|
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
stream.pipe(file)
|
stream.pipe(file)
|
||||||
@ -242,7 +212,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
protected async scriptIsLatest(scriptPath: string) {
|
protected async scriptIsLatest(scriptPath: string) {
|
||||||
const scriptExists = await pathExists(scriptPath)
|
const scriptExists = await pathExists(scriptPath)
|
||||||
if (!scriptExists) return false
|
if(!scriptExists) return false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filehandle = await fs.promises.open(scriptPath, 'r')
|
const filehandle = await fs.promises.open(scriptPath, 'r')
|
||||||
@ -261,7 +231,7 @@ export class Kubectl {
|
|||||||
const fsPromises = fs.promises;
|
const fsPromises = fs.promises;
|
||||||
const bashScriptPath = path.join(this.dirname, '.bash_set_path')
|
const bashScriptPath = path.join(this.dirname, '.bash_set_path')
|
||||||
const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath)
|
const bashScriptIsLatest = await this.scriptIsLatest(bashScriptPath)
|
||||||
if (!bashScriptIsLatest) {
|
if(!bashScriptIsLatest) {
|
||||||
let bashScript = "" + initScriptVersionString
|
let bashScript = "" + initScriptVersionString
|
||||||
bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
|
bashScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
|
||||||
bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n"
|
bashScript += "test -f \"/etc/profile\" && . \"/etc/profile\"\n"
|
||||||
@ -280,7 +250,7 @@ export class Kubectl {
|
|||||||
|
|
||||||
const zshScriptPath = path.join(this.dirname, '.zlogin')
|
const zshScriptPath = path.join(this.dirname, '.zlogin')
|
||||||
const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath)
|
const zshScriptIsLatest = await this.scriptIsLatest(zshScriptPath)
|
||||||
if (!zshScriptIsLatest) {
|
if(!zshScriptIsLatest) {
|
||||||
let zshScript = "" + initScriptVersionString
|
let zshScript = "" + initScriptVersionString
|
||||||
|
|
||||||
zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
|
zshScript += "tempkubeconfig=\"$KUBECONFIG\"\n"
|
||||||
@ -314,13 +284,11 @@ export class Kubectl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected getDownloadMirror() {
|
protected getDownloadMirror() {
|
||||||
if (isMac) {
|
if (process.platform == "darwin") {
|
||||||
return packageMirrors.get("default") // MacOS packages are only available from default
|
return packageMirrors.get("default") // MacOS packages are only available from default
|
||||||
}
|
}
|
||||||
const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror)
|
const mirror = packageMirrors.get(userStore.getPreferences().downloadMirror)
|
||||||
if (mirror) {
|
if (mirror) { return mirror }
|
||||||
return mirror
|
|
||||||
}
|
|
||||||
|
|
||||||
return packageMirrors.get("default")
|
return packageMirrors.get("default")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,34 @@
|
|||||||
import shellEnv from "shell-env"
|
import shellEnv from "shell-env"
|
||||||
import logger from "./logger"
|
import os from "os";
|
||||||
import { isMac, isProduction } from "../common/vars";
|
|
||||||
|
|
||||||
export async function shellSync() {
|
interface Env {
|
||||||
const env = await shellEnv()
|
[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
|
// Overwrite PATH on darwin
|
||||||
if (isProduction && isMac) {
|
if (process.env.NODE_ENV === "production" && process.platform === "darwin") {
|
||||||
process.env["PATH"] = env.PATH
|
process.env["PATH"] = env.PATH
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = null
|
// The spread operator allows joining of objects. The precedence is last to first.
|
||||||
for (key in env) {
|
process.env = {
|
||||||
if (!env.hasOwnProperty(key) || process.env[key]) continue // skip existing and prototype keys
|
...env,
|
||||||
logger.debug("Imported " + key + " from login shell to process environment")
|
...process.env,
|
||||||
process.env[key] = env[key]
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { IKubeApiLinkBase, KubeApi } from "../kube-api";
|
|||||||
|
|
||||||
interface ParseAPITest {
|
interface ParseAPITest {
|
||||||
url: string;
|
url: string;
|
||||||
expected: IKubeApiLinkBase;
|
expected: Required<IKubeApiLinkBase>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tests: ParseAPITest[] = [
|
const tests: ParseAPITest[] = [
|
||||||
@ -97,6 +97,19 @@ const tests: ParseAPITest[] = [
|
|||||||
namespace: undefined,
|
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');
|
jest.mock('../kube-watch-api.ts', () => 'KubeWatchApi');
|
||||||
|
|||||||
@ -42,7 +42,6 @@ export interface IKubeApiLinkBase extends IKubeApiLinkRef {
|
|||||||
export class KubeApi<T extends KubeObject = any> {
|
export class KubeApi<T extends KubeObject = any> {
|
||||||
static parseApi(apiPath = ""): IKubeApiLinkBase {
|
static parseApi(apiPath = ""): IKubeApiLinkBase {
|
||||||
apiPath = new URL(apiPath, location.origin).pathname;
|
apiPath = new URL(apiPath, location.origin).pathname;
|
||||||
|
|
||||||
const [, prefix, ...parts] = apiPath.split("/");
|
const [, prefix, ...parts] = apiPath.split("/");
|
||||||
const apiPrefix = `/${prefix}`;
|
const apiPrefix = `/${prefix}`;
|
||||||
|
|
||||||
@ -51,11 +50,11 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
|
|
||||||
if (namespaced) {
|
if (namespaced) {
|
||||||
switch (right.length) {
|
switch (right.length) {
|
||||||
case 0:
|
|
||||||
resource = "namespaces"; // special case this due to `split` removing namespaces
|
|
||||||
break;
|
|
||||||
case 1:
|
case 1:
|
||||||
resource = right[0];
|
name = right[0];
|
||||||
|
// fallthroughcase 0:
|
||||||
|
resource = "namespaces"; // special case this due to `split` removing namespaces
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
[namespace, resource, name] = right;
|
[namespace, resource, name] = right;
|
||||||
@ -68,7 +67,7 @@ export class KubeApi<T extends KubeObject = any> {
|
|||||||
switch (left.length) {
|
switch (left.length) {
|
||||||
case 2:
|
case 2:
|
||||||
resource = left.pop();
|
resource = left.pop();
|
||||||
case 1:
|
// fallthroughcase 1:
|
||||||
apiVersion = left.pop();
|
apiVersion = left.pop();
|
||||||
apiGroup = "";
|
apiGroup = "";
|
||||||
break;
|
break;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user