diff --git a/package-lock.json b/package-lock.json index 2acaad66d7..1876c8f150 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,6 @@ "form-data": "^4.0.0", "hpagent": "^1.2.0", "isomorphic-ws": "^5.0.0", - "js-yaml": "^4.1.0", "jsonpath-plus": "^10.3.0", "node-fetch": "^2.7.0", "openid-client": "^6.1.3", @@ -24,7 +23,8 @@ "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar-fs": "^3.0.9", - "ws": "^8.18.2" + "ws": "^8.18.2", + "yaml": "^2.8.1" }, "devDependencies": { "@eslint/js": "^9.18.0", @@ -1374,6 +1374,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/asynckit": { @@ -2686,6 +2687,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4055,7 +4057,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", - "dev": true, "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 6d15d2e893..a8ca03a178 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "form-data": "^4.0.0", "hpagent": "^1.2.0", "isomorphic-ws": "^5.0.0", - "js-yaml": "^4.1.0", "jsonpath-plus": "^10.3.0", "node-fetch": "^2.7.0", "openid-client": "^6.1.3", @@ -70,7 +69,8 @@ "socks-proxy-agent": "^8.0.4", "stream-buffers": "^3.0.2", "tar-fs": "^3.0.9", - "ws": "^8.18.2" + "ws": "^8.18.2", + "yaml": "^2.8.1" }, "devDependencies": { "@eslint/js": "^9.18.0", diff --git a/src/types.ts b/src/types.ts index 11f091cfbf..932047d22a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,6 +13,15 @@ export interface KubernetesListObject { items: T[]; } +export type YamlParseOptions = { + version?: '1.1' | '1.2'; + maxAliasCount?: number; + prettyErrors?: boolean; + keepCstNodes?: boolean; + keepNodeTypes?: boolean; + logLevel?: 'silent' | 'error' | 'warn'; +}; + export type IntOrString = number | string; export class V1MicroTime extends Date { diff --git a/src/yaml.ts b/src/yaml.ts index 09468a3e5d..0dda9a4b6d 100644 --- a/src/yaml.ts +++ b/src/yaml.ts @@ -1,6 +1,6 @@ -import yaml from 'js-yaml'; +import yaml from 'yaml'; import { getSerializationType } from './util.js'; -import { KubernetesObject } from './types.js'; +import { KubernetesObject, YamlParseOptions } from './types.js'; import { ObjectSerializer } from './serializer.js'; /** @@ -9,8 +9,8 @@ import { ObjectSerializer } from './serializer.js'; * @param opts - Optional YAML load options. * @returns The deserialized Kubernetes object. */ -export function loadYaml(data: string, opts?: yaml.LoadOptions): T { - const yml = yaml.load(data, opts) as any as KubernetesObject; +export function loadYaml(data: string, opts?: YamlParseOptions): T { + const yml = yaml.parse(data, { version: '1.1', ...opts }) as any as KubernetesObject; if (!yml) { throw new Error('Failed to load YAML'); } @@ -25,12 +25,12 @@ export function loadYaml(data: string, opts?: yaml.LoadOptions): T { * @param opts - Optional YAML load options. * @returns An array of deserialized Kubernetes objects. */ -export function loadAllYaml(data: string, opts?: yaml.LoadOptions): any[] { - const ymls = yaml.loadAll(data, undefined, opts); +export function loadAllYaml(data: string, opts?: YamlParseOptions): any[] { + const ymls = yaml.parseAllDocuments(data, { version: '1.1', ...opts }); return ymls.map((yml) => { - const obj = yml as KubernetesObject; + const obj = yml.toJS() as KubernetesObject; const type = getSerializationType(obj.apiVersion, obj.kind); - return ObjectSerializer.deserialize(yml, type); + return ObjectSerializer.deserialize(obj, type); }); } @@ -40,9 +40,9 @@ export function loadAllYaml(data: string, opts?: yaml.LoadOptions): any[] { * @param opts - Optional YAML dump options. * @returns The YAML string representation of the serialized Kubernetes object. */ -export function dumpYaml(object: any, opts?: yaml.DumpOptions): string { +export function dumpYaml(object: any, opts?: YamlParseOptions): string { const kubeObject = object as KubernetesObject; const type = getSerializationType(kubeObject.apiVersion, kubeObject.kind); const serialized = ObjectSerializer.serialize(kubeObject, type); - return yaml.dump(serialized, opts); + return yaml.stringify(serialized, { version: '1.1', ...opts }); } diff --git a/src/yaml_test.ts b/src/yaml_test.ts index 958f7ba45b..9c473088ca 100644 --- a/src/yaml_test.ts +++ b/src/yaml_test.ts @@ -154,4 +154,55 @@ spec: // not using strict equality as types are not matching deepEqual(actual, expected); }); + + it('should parse octal values correctly using default YAML 1.1', () => { + const yamlStr = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + mode: 0644 +`; + const obj = loadYaml<{ + data: { mode: number }; + metadata: { name: string }; + apiVersion: string; + kind: string; + }>(yamlStr); + strictEqual(obj.data.mode, 420); + }); + + it('should treat octal as string if version 1.2 is provided', () => { + const yamlStr = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: test +data: + mode: '0644' +`; + + const objs = loadAllYaml(yamlStr, { version: '1.2', maxAliasCount: 100, prettyErrors: true }); + + strictEqual(objs[0].data.mode, '0644'); + }); + + it('should load multiple documents with default YAML 1.1', () => { + const yamlStr = ` +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm2 +`; + const objs = loadAllYaml(yamlStr) as Array<{ metadata: { name: string } }>; + strictEqual(objs.length, 2); + strictEqual(objs[0].metadata.name, 'cm1'); + strictEqual(objs[1].metadata.name, 'cm2'); + }); });