diff --git a/package.json b/package.json index 27d033db6b..057390be8e 100644 --- a/package.json +++ b/package.json @@ -207,7 +207,7 @@ "http-proxy": "^1.18.1", "immer": "^9.0.6", "joi": "^17.4.2", - "js-yaml": "^3.14.0", + "js-yaml": "^4.1.0", "jsdom": "^16.7.0", "jsonpath": "^1.1.1", "lodash": "^4.17.15", @@ -277,7 +277,7 @@ "@types/html-webpack-plugin": "^3.2.6", "@types/http-proxy": "^1.17.7", "@types/jest": "^26.0.24", - "@types/js-yaml": "^3.12.4", + "@types/js-yaml": "^4.0.2", "@types/jsdom": "^16.2.13", "@types/jsonpath": "^0.2.0", "@types/lodash": "^4.14.155", diff --git a/src/common/__tests__/cluster-store.test.ts b/src/common/__tests__/cluster-store.test.ts index 620debbb0b..8bb920de73 100644 --- a/src/common/__tests__/cluster-store.test.ts +++ b/src/common/__tests__/cluster-store.test.ts @@ -426,7 +426,7 @@ describe("pre 2.6.0 config with a cluster that has arrays in auth config", () => it("replaces array format access token and expiry into string", async () => { const file = ClusterStore.getInstance().clustersList[0].kubeConfigPath; const config = fs.readFileSync(file, "utf8"); - const kc = yaml.safeLoad(config); + const kc = yaml.load(config) as Record; expect(kc.users[0].user["auth-provider"].config["access-token"]).toBe("should be string"); expect(kc.users[0].user["auth-provider"].config["expiry"]).toBe("should be string"); diff --git a/src/common/k8s-api/endpoints/helm-releases.api.ts b/src/common/k8s-api/endpoints/helm-releases.api.ts index 301e077e4e..7d6f45fa5c 100644 --- a/src/common/k8s-api/endpoints/helm-releases.api.ts +++ b/src/common/k8s-api/endpoints/helm-releases.api.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import { autoBind, formatDuration } from "../../utils"; import capitalize from "lodash/capitalize"; import { apiBase } from "../index"; @@ -113,21 +113,31 @@ export async function getRelease(name: string, namespace: string): Promise { - const { repo, ...data } = payload; + const { repo, chart: rawChart, values: rawValues, ...data } = payload; + const chart = `${repo}/${rawChart}`; + const values = yaml.load(rawValues); - data.chart = `${repo}/${data.chart}`; - data.values = jsYaml.safeLoad(data.values); - - return apiBase.post(endpoint(), { data }); + return apiBase.post(endpoint(), { + data: { + chart, + values, + ...data, + } + }); } export async function updateRelease(name: string, namespace: string, payload: IReleaseUpdatePayload): Promise { - const { repo, ...data } = payload; + const { repo, chart: rawChart, values: rawValues, ...data } = payload; + const chart = `${repo}/${rawChart}`; + const values = yaml.load(rawValues); - data.chart = `${repo}/${data.chart}`; - data.values = jsYaml.safeLoad(data.values); - - return apiBase.put(endpoint({ name, namespace }), { data }); + return apiBase.put(endpoint({ name, namespace }), { + data: { + chart, + values, + ...data, + } + }); } export async function deleteRelease(name: string, namespace: string): Promise { diff --git a/src/common/k8s-api/endpoints/resource-applier.api.ts b/src/common/k8s-api/endpoints/resource-applier.api.ts index 14b5d5ad46..ea86c266a2 100644 --- a/src/common/k8s-api/endpoints/resource-applier.api.ts +++ b/src/common/k8s-api/endpoints/resource-applier.api.ts @@ -19,7 +19,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import type { KubeJsonApiData } from "../kube-json-api"; import { apiBase } from "../index"; import type { Patch } from "rfc6902"; @@ -30,7 +30,13 @@ export const annotations = [ export async function update(resource: object | string): Promise { if (typeof resource === "string") { - resource = jsYaml.safeLoad(resource); + const parsed = yaml.load(resource); + + if (typeof parsed !== "object") { + throw new Error("Cannot update resource to string or number"); + } + + resource = parsed; } return apiBase.post("/stack", { data: resource }); diff --git a/src/common/k8s/resource-stack.ts b/src/common/k8s/resource-stack.ts index 9ea9d2609f..650650ff92 100644 --- a/src/common/k8s/resource-stack.ts +++ b/src/common/k8s/resource-stack.ts @@ -133,7 +133,7 @@ export class ResourceStack { if (!resourceData.trim()) continue; - const resourceArray = yaml.safeLoadAll(resourceData.toString()); + const resourceArray = yaml.loadAll(resourceData.toString()); resourceArray.forEach((resource) => { if (resource?.metadata) { @@ -143,7 +143,7 @@ export class ResourceStack { resource.metadata.labels["app.kubernetes.io/created-by"] = "resource-stack"; } - resources.push(yaml.safeDump(resource)); + resources.push(yaml.dump(resource)); }); } diff --git a/src/common/kube-helpers.ts b/src/common/kube-helpers.ts index 16046b4676..1b91a35e56 100644 --- a/src/common/kube-helpers.ts +++ b/src/common/kube-helpers.ts @@ -111,7 +111,7 @@ interface OptionsResult { } function loadToOptions(rawYaml: string): OptionsResult { - const parsed = yaml.safeLoad(rawYaml); + const parsed = yaml.load(rawYaml); const { error } = kubeConfigSchema.validate(parsed, { abortEarly: false, allowUnknown: true, @@ -248,7 +248,7 @@ export function dumpConfigYaml(kubeConfig: Partial): string { logger.debug("Dumping KubeConfig:", config); // skipInvalid: true makes dump ignore undefined values - return yaml.safeDump(config, { skipInvalid: true }); + return yaml.dump(config, { skipInvalid: true }); } /** diff --git a/src/main/helm/helm-chart-manager.ts b/src/main/helm/helm-chart-manager.ts index b8261845db..e99554dcd0 100644 --- a/src/main/helm/helm-chart-manager.ts +++ b/src/main/helm/helm-chart-manager.ts @@ -29,6 +29,11 @@ import { helmCli } from "./helm-cli"; import type { RepoHelmChartList } from "../../common/k8s-api/endpoints/helm-charts.api"; import { sortCharts } from "../../common/utils"; +export interface HelmCacheFile { + apiVersion: string; + entries: RepoHelmChartList; +} + export class HelmChartManager { static #cache = new Map(); @@ -82,7 +87,11 @@ export class HelmChartManager { protected async cachedYaml(): Promise { if (!HelmChartManager.#cache.has(this.repo.name)) { const cacheFile = await fs.promises.readFile(this.repo.cacheFilePath, "utf-8"); - const { entries } = yaml.safeLoad(cacheFile) as { entries: RepoHelmChartList }; + const data = yaml.load(cacheFile) as string | number | HelmCacheFile; + + if (typeof data !== "object" || !data) { + return {}; + } /** * Do some initial preprocessing on the data, so as to avoid needing to do it later @@ -92,7 +101,7 @@ export class HelmChartManager { */ const normalized = Object.fromEntries( - Object.entries(entries) + Object.entries(data.entries) .map(([name, charts]) => [ name, sortCharts( diff --git a/src/main/helm/helm-release-manager.ts b/src/main/helm/helm-release-manager.ts index 571628789e..d0ca2e8427 100644 --- a/src/main/helm/helm-release-manager.ts +++ b/src/main/helm/helm-release-manager.ts @@ -53,7 +53,7 @@ export async function installChart(chart: string, values: any, name: string | un const helm = await helmCli.binaryPath(); const fileName = tempy.file({ name: "values.yaml" }); - await fse.writeFile(fileName, yaml.safeDump(values)); + await fse.writeFile(fileName, yaml.dump(values)); try { let generateName = ""; @@ -83,7 +83,7 @@ export async function upgradeRelease(name: string, chart: string, values: any, n const helm = await helmCli.binaryPath(); const fileName = tempy.file({ name: "values.yaml" }); - await fse.writeFile(fileName, yaml.safeDump(values)); + await fse.writeFile(fileName, yaml.dump(values)); try { const proxyKubeconfig = await cluster.getProxyKubeconfigPath(); diff --git a/src/main/helm/helm-repo-manager.ts b/src/main/helm/helm-repo-manager.ts index 9e4038a10f..2da7cfff18 100644 --- a/src/main/helm/helm-repo-manager.ts +++ b/src/main/helm/helm-repo-manager.ts @@ -101,18 +101,28 @@ export class HelmRepoManager extends Singleton { return repos.find(repo => repo.name === name); } + private async readConfig(): Promise { + try { + const rawConfig = await readFile(this.helmEnv.HELM_REPOSITORY_CONFIG, "utf8"); + const parsedConfig = yaml.load(rawConfig); + + if (typeof parsedConfig === "object" && parsedConfig) { + return parsedConfig as HelmRepoConfig; + } + } catch { } + + return { + repositories: [] + }; + } + public async repositories(): Promise { try { if (!this.initialized) { await this.init(); } - const repoConfigFile = this.helmEnv.HELM_REPOSITORY_CONFIG; - const { repositories }: HelmRepoConfig = await readFile(repoConfigFile, "utf8") - .then((yamlContent: string) => yaml.safeLoad(yamlContent)) - .catch(() => ({ - repositories: [] - })); + const { repositories } = await this.readConfig(); if (!repositories.length) { await HelmRepoManager.addRepo({ name: "bitnami", url: "https://charts.bitnami.com/bitnami" }); diff --git a/src/main/resource-applier.ts b/src/main/resource-applier.ts index a305ff1091..e6200b9d30 100644 --- a/src/main/resource-applier.ts +++ b/src/main/resource-applier.ts @@ -78,7 +78,7 @@ export class ResourceApplier { resource = this.sanitizeObject(resource); appEventBus.emit({ name: "resource", action: "apply" }); - return this.kubectlApply(yaml.safeDump(resource)); + return this.kubectlApply(yaml.dump(resource)); } protected async kubectlApply(content: string): Promise { diff --git a/src/migrations/cluster-store/2.6.0-beta.3.ts b/src/migrations/cluster-store/2.6.0-beta.3.ts index 75662582e5..60373d756a 100644 --- a/src/migrations/cluster-store/2.6.0-beta.3.ts +++ b/src/migrations/cluster-store/2.6.0-beta.3.ts @@ -32,9 +32,13 @@ export default { const cluster = value[1]; if (!cluster.kubeConfig) continue; - const kubeConfig = yaml.safeLoad(cluster.kubeConfig); + const config = yaml.load(cluster.kubeConfig); - if (!kubeConfig.hasOwnProperty("users")) continue; + if (!config || typeof config !== "object" || !config.hasOwnProperty("users")) { + continue; + } + + const kubeConfig = config as Record; const userObj = kubeConfig.users[0]; if (userObj) { @@ -56,7 +60,7 @@ export default { name: userObj.name, user }]; - cluster.kubeConfig = yaml.safeDump(kubeConfig); + cluster.kubeConfig = yaml.dump(kubeConfig); store.set(clusterKey, cluster); } } diff --git a/src/renderer/components/+workloads-pods/pod-details-affinities.tsx b/src/renderer/components/+workloads-pods/pod-details-affinities.tsx index 01cd5ea0b4..dee24f5056 100644 --- a/src/renderer/components/+workloads-pods/pod-details-affinities.tsx +++ b/src/renderer/components/+workloads-pods/pod-details-affinities.tsx @@ -21,7 +21,7 @@ import "./pod-details-affinities.scss"; import React from "react"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import { DrawerParamToggler, DrawerItem } from "../drawer"; import type { Pod, Deployment, DaemonSet, StatefulSet, ReplicaSet, Job } from "../../../common/k8s-api/endpoints"; import MonacoEditor from "react-monaco-editor"; @@ -50,7 +50,7 @@ export class PodDetailsAffinities extends React.Component { className={cssNames("MonacoEditor")} theme={ThemeStore.getInstance().activeTheme.monacoTheme} language="yaml" - value={jsYaml.dump(affinities)} + value={yaml.dump(affinities)} /> diff --git a/src/renderer/components/dock/create-resource.tsx b/src/renderer/components/dock/create-resource.tsx index c7de411e3c..1ef55ba1cb 100644 --- a/src/renderer/components/dock/create-resource.tsx +++ b/src/renderer/components/dock/create-resource.tsx @@ -25,7 +25,7 @@ import React from "react"; import path from "path"; import fs from "fs-extra"; import {Select, GroupSelectOption, SelectOption} from "../select"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import { cssNames } from "../../utils"; @@ -101,15 +101,14 @@ export class CreateResource extends React.Component { return null; } - // skip empty documents if "---" pasted at the beginning or end - const resources = jsYaml.safeLoadAll(this.data).filter(Boolean); + // skip empty documents + const resources = yaml.loadAll(this.data).filter(Boolean); + const createdResources: string[] = []; if (resources.length === 0) { return void logger.info("Nothing to create"); } - const createdResources: string[] = []; - for (const result of await Promise.allSettled(resources.map(resourceApplierApi.update))) { if (result.status === "fulfilled") { createdResources.push(result.value.metadata.name); diff --git a/src/renderer/components/dock/edit-resource.tsx b/src/renderer/components/dock/edit-resource.tsx index 068855eebb..06918272bd 100644 --- a/src/renderer/components/dock/edit-resource.tsx +++ b/src/renderer/components/dock/edit-resource.tsx @@ -72,13 +72,13 @@ export class EditResource extends React.Component { return draft; } - return yaml.safeDump(this.resource.toPlainObject()); // dump resource first time + return yaml.dump(this.resource.toPlainObject()); // dump resource first time } @action saveDraft(draft: string | object) { if (typeof draft === "object") { - draft = draft ? yaml.safeDump(draft) : undefined; + draft = draft ? yaml.dump(draft) : undefined; } editResourceStore.setData(this.tabId, { @@ -99,8 +99,8 @@ export class EditResource extends React.Component { } const store = editResourceStore.getStore(this.tabId); - const currentVersion = yaml.safeLoad(this.draft); - const firstVersion = yaml.safeLoad(editResourceStore.getData(this.tabId).firstDraft ?? this.draft); + const currentVersion = yaml.load(this.draft); + const firstVersion = yaml.load(editResourceStore.getData(this.tabId).firstDraft ?? this.draft); const patches = createPatch(firstVersion, currentVersion); const updatedResource = await store.patch(this.resource, patches); diff --git a/src/renderer/components/dock/editor-panel.tsx b/src/renderer/components/dock/editor-panel.tsx index bbf0b96e53..aa941b2e2e 100644 --- a/src/renderer/components/dock/editor-panel.tsx +++ b/src/renderer/components/dock/editor-panel.tsx @@ -21,7 +21,7 @@ import MonacoEditor, {monaco} from "react-monaco-editor"; import React from "react"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import { observable, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import { dockStore, TabId } from "./dock.store"; @@ -63,14 +63,14 @@ export class EditorPanel extends React.Component { editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor) => { this.editor = editor; const model = monacoModelsManager.getModel(this.props.tabId); - + model.setValue(this.props.value ?? ""); this.editor.setModel(model); }; validate(value: string) { try { - jsYaml.safeLoadAll(value); + yaml.loadAll(value); this.yamlError = ""; } catch (err) { this.yamlError = err.toString(); diff --git a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx index 2eb4298b47..a83a79a44e 100644 --- a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx +++ b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx @@ -24,7 +24,7 @@ import "./kubeconfig-dialog.scss"; import React from "react"; import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; -import jsYaml from "js-yaml"; +import yaml from "js-yaml"; import type { ServiceAccount } from "../../../common/k8s-api/endpoints"; import { copyToClipboard, cssNames, saveFileDialog } from "../../utils"; import { Button } from "../button"; @@ -88,7 +88,7 @@ export class KubeConfigDialog extends React.Component { this.close(); }); - this.config = config ? jsYaml.dump(config) : ""; + this.config = config ? yaml.dump(config) : ""; } copyToClipboard = () => { diff --git a/yarn.lock b/yarn.lock index 7a75fdfba3..596615a1da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1611,12 +1611,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/js-yaml@^3.12.4": - version "3.12.4" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.4.tgz#7d3b534ec35a0585128e2d332db1403ebe057e25" - integrity sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A== - -"@types/js-yaml@^4.0.1": +"@types/js-yaml@^4.0.1", "@types/js-yaml@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.2.tgz#4117a7a378593a218e9d6f0ef44ce6d5d9edf7fa" integrity sha512-KbeHS/Y4R+k+5sWXEYzAZKuB1yQlZtEghuhRxrVRLaqhtoG5+26JwQsa4HyS3AWX8v1Uwukma5HheduUDskasA== @@ -8468,7 +8463,7 @@ js-sha3@^0.8.0: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.13.1, js-yaml@^3.14.0: +js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==