Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,15 @@
"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",
"rfc4648": "^1.3.0",
"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",
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export interface KubernetesListObject<T extends KubernetesObject> {
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 {
Expand Down
20 changes: 10 additions & 10 deletions src/yaml.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -9,8 +9,8 @@ import { ObjectSerializer } from './serializer.js';
* @param opts - Optional YAML load options.
* @returns The deserialized Kubernetes object.
*/
export function loadYaml<T>(data: string, opts?: yaml.LoadOptions): T {
const yml = yaml.load(data, opts) as any as KubernetesObject;
export function loadYaml<T>(data: string, opts?: YamlParseOptions): T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is still a breaking change to end users. Prior to this change, they could pass in LoadOptions from the js-yaml library. After this change, the available options are different and there is no logic to support the old options.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cjihrig ,I have reviewed the documentation for both libraries and compared their options. I found that most of the options have the same functionality, but a few methods differ in behavior. Do you think we could use the Adapter Design Pattern to address this issue and maintain compatibility while migrating to the new library?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to decide what options we want to support and go from there. If we want to support the options from the previous library, then we will need an adapter. If we want to support the options from the new library, then we'll need to hold this PR until the next major release. I think no matter which route we go, we should define our own type for the options (as you've done here) so we can help insulate ourselves from this sort of problem in the future.

cc: @brendandburns @mstruebing

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that’s clear. Let’s hold off until we get feedback from @brendandburns and @mstruebing about which option set we want to support going forward.

Copy link
Member

@mstruebing mstruebing Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely think we should define our own options as @cjihrig already said.
I would think we should make this PR fine and hold it until the next release except someone thinks it's worth to put in the effort to make this mergeable without a breaking change, but I would be fine with waiting.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @cjihrig @mstruebing, I understand your point. It might take me some time, but I’d like to work on modifying this PR to avoid the breaking change. I’ll try to achieve that by adding an adapter layer to maintain backward compatibility.

const yml = yaml.parse(data, { version: '1.1', ...opts }) as any as KubernetesObject;
if (!yml) {
throw new Error('Failed to load YAML');
}
Expand All @@ -25,12 +25,12 @@ export function loadYaml<T>(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);
});
}

Expand All @@ -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 });
}
51 changes: 51 additions & 0 deletions src/yaml_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should parse octal values correctly using default YAML 1.1', () => {
it('should parse octal values as numbers 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', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why this test is so much different from the one before it. Couldn't it be the exact same setup, but with a different YAML version specified and a different expected value in the assertion?

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');
});
});