From f620b3198fc9482074f8145481ac80324eee0866 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 23 Jun 2020 08:37:35 -0400 Subject: [PATCH 1/3] Fix pasting unicode into terminal sometimes not working (#492) * add LANG env with UTF-8 specified * fix precedence of union of env objects Signed-off-by: Sebastian Malton Co-authored-by: Sebastian Malton --- src/main/index.ts | 2 +- src/main/shell-sync.ts | 33 +++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index b1e43a4928..0e94fb5226 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -34,7 +34,7 @@ const vmURL = (isDevelopment) ? `http://localhost:${process.env.ELECTRON_WEBPACK }) async function main() { - await shellSync() + shellSync(app.getLocale()) const updater = new AppUpdater() updater.start(); diff --git a/src/main/shell-sync.ts b/src/main/shell-sync.ts index 39845c7b01..afeccaa657 100644 --- a/src/main/shell-sync.ts +++ b/src/main/shell-sync.ts @@ -1,18 +1,35 @@ import shellEnv = require("shell-env") import logger from "./logger" +import * as 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 (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, + }; } From 6770f05dc8b11143fa68753b1e6e9d6e44a99000 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 23 Jun 2020 08:48:20 -0400 Subject: [PATCH 2/3] special case */namespace/* in parseApi, add test (#509) --- .../client/api/__test__/parseAPI.test.ts | 15 +++++- dashboard/client/api/kube-api.ts | 47 +++++++++---------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/dashboard/client/api/__test__/parseAPI.test.ts b/dashboard/client/api/__test__/parseAPI.test.ts index b748098622..e700650e05 100644 --- a/dashboard/client/api/__test__/parseAPI.test.ts +++ b/dashboard/client/api/__test__/parseAPI.test.ts @@ -2,7 +2,7 @@ import { KubeApi, IKubeApiLinkBase } from "../kube-api"; interface ParseAPITest { url: string; - expected: IKubeApiLinkBase; + expected: Required; } 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'); diff --git a/dashboard/client/api/kube-api.ts b/dashboard/client/api/kube-api.ts index b77a538d14..e6668084f5 100644 --- a/dashboard/client/api/kube-api.ts +++ b/dashboard/client/api/kube-api.ts @@ -8,7 +8,6 @@ import { apiKube } from "./index"; import { kubeWatchApi } from "./kube-watch-api"; import { apiManager } from "./api-manager"; import { split } from "../utils/arrays"; -import isEqual from "lodash/isEqual"; export interface IKubeApiOptions { kind: string; // resource type within api-group, e.g. "Namespace" @@ -43,7 +42,6 @@ export interface IKubeApiLinkBase extends IKubeApiLinkRef { export class KubeApi { static parseApi(apiPath = ""): IKubeApiLinkBase { apiPath = new URL(apiPath, location.origin).pathname; - const [, prefix, ...parts] = apiPath.split("/"); const apiPrefix = `/${prefix}`; @@ -52,12 +50,12 @@ export class KubeApi { if (namespaced) { switch (right.length) { + case 1: + name = right[0]; + // fallthrough case 0: resource = "namespaces"; // special case this due to `split` removing namespaces break; - case 1: - resource = right[0]; - break; default: [namespace, resource, name] = right; break; @@ -69,27 +67,28 @@ export class KubeApi { switch (left.length) { case 2: resource = left.pop(); + // fallthrough case 1: apiVersion = left.pop(); apiGroup = ""; break; default: /** - * Given that - * - `apiVersion` is `GROUP/VERSION` and - * - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i - * where length <= 63 - * - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253 - * - * There is no well defined selection from an array of items that were - * seperated by '/' - * - * Solution is to create a huristic. Namely: - * 1. if '.' in left[0] then apiGroup <- left[0] - * 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1] - * 3. otherwise assume apiVersion <- left[0] - * 4. always resource, name <- left[(0 or 1)+1..] - */ + * Given that + * - `apiVersion` is `GROUP/VERSION` and + * - `VERSION` is `DNS_LABEL` which is /^[a-z0-9]((-[a-z0-9])|[a-z0-9])*$/i + * where length <= 63 + * - `GROUP` is /^D(\.D)*$/ where D is `DNS_LABEL` and length <= 253 + * + * There is no well defined selection from an array of items that were + * seperated by '/' + * + * Solution is to create a huristic. Namely: + * 1. if '.' in left[0] then apiGroup <- left[0] + * 2. if left[1] matches /^v[0-9]/ then apiGroup, apiVersion <- left[0], left[1] + * 3. otherwise assume apiVersion <- left[0] + * 4. always resource, name <- left[(0 or 1)+1..] + */ if (left[0].includes('.') || left[1].match(/^v[0-9]/)) { [apiGroup, apiVersion] = left; resource = left.slice(2).join("/") @@ -199,7 +198,7 @@ export class KubeApi { if (KubeObject.isJsonApiData(data)) { return new KubeObjectConstructor(data); } - + // process items list response if (KubeObject.isJsonApiDataList(data)) { const { apiVersion, items, metadata } = data; @@ -211,12 +210,12 @@ export class KubeApi { ...item, })) } - + // custom apis might return array for list response, e.g. users, groups, etc. if (Array.isArray(data)) { return data.map(data => new KubeObjectConstructor(data)); } - + return data; } @@ -234,7 +233,7 @@ export class KubeApi { async create({ name = "", namespace = "default" } = {}, data?: Partial): Promise { const apiUrl = this.getUrl({ namespace }); - + return this.request .post(apiUrl, { data: merge({ From cd66a985cc10501f1944598de341d8d23c7e9de7 Mon Sep 17 00:00:00 2001 From: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com> Date: Tue, 23 Jun 2020 08:54:18 -0400 Subject: [PATCH 3/3] modified Kubectl.checkBinary() to test the binary for the right version instead of using the eTag (#510) Signed-off-by: Jim Ehrismann --- src/main/kubectl.ts | 49 ++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/main/kubectl.ts b/src/main/kubectl.ts index f72bcb5336..7939799b3e 100644 --- a/src/main/kubectl.ts +++ b/src/main/kubectl.ts @@ -2,7 +2,7 @@ import { app, remote } from "electron" import * as path from "path" import * as fs from "fs" import * as request from "request" -import * as requestPromise from "request-promise-native" +import { promiseExec} from "./promise-exec" import logger from "./logger" import { ensureDir, pathExists } from "fs-extra" import * as md5File from "md5-file" @@ -117,42 +117,31 @@ export class Kubectl { } } - 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 }