Skip to content

Commit 8c293c3

Browse files
Fix Knative Service YAML parsing by checking API groups
Co-authored-by: brendandburns <[email protected]>
1 parent 33f49e1 commit 8c293c3

File tree

5 files changed

+113
-8
lines changed

5 files changed

+113
-8
lines changed

src/object.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class KubernetesObjectApi {
103103
if (fieldManager !== undefined) {
104104
requestContext.setQueryParam('fieldManager', ObjectSerializer.serialize(fieldManager, 'string'));
105105
}
106-
const type = getSerializationType(spec.apiVersion, spec.kind);
106+
const type = getSerializationType(spec.apiVersion, spec.kind) ?? 'KubernetesObject';
107107

108108
// Body Params
109109
const contentType = ObjectSerializer.getPreferredMediaType([]);
@@ -265,7 +265,7 @@ export class KubernetesObjectApi {
265265
requestContext.setQueryParam('force', ObjectSerializer.serialize(force, 'boolean'));
266266
}
267267

268-
const type = getSerializationType(spec.apiVersion, spec.kind);
268+
const type = getSerializationType(spec.apiVersion, spec.kind) ?? 'KubernetesObject';
269269

270270
// Body Params
271271
const serializedBody = ObjectSerializer.stringify(
@@ -464,7 +464,7 @@ export class KubernetesObjectApi {
464464
requestContext.setQueryParam('fieldManager', ObjectSerializer.serialize(fieldManager, 'string'));
465465
}
466466

467-
const type = getSerializationType(spec.apiVersion, spec.kind);
467+
const type = getSerializationType(spec.apiVersion, spec.kind) ?? 'KubernetesObject';
468468

469469
// Body Params
470470
const contentType = ObjectSerializer.getPreferredMediaType([]);
@@ -642,7 +642,7 @@ export class KubernetesObjectApi {
642642
if (response.httpStatusCode >= 200 && response.httpStatusCode <= 299) {
643643
const data = ObjectSerializer.parse(await response.body.text(), contentType);
644644
if (type === undefined) {
645-
type = getSerializationType(data.apiVersion, data.kind);
645+
type = getSerializationType(data.apiVersion, data.kind) ?? 'KubernetesObject';
646646
}
647647

648648
if (!type) {

src/util.ts

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,58 @@ export function normalizeResponseHeaders(response: Response): { [key: string]: s
165165
return normalizedHeaders;
166166
}
167167

168-
export function getSerializationType(apiVersion?: string, kind?: string): string {
168+
/**
169+
* Built-in Kubernetes API groups that have generated TypeScript models.
170+
* Custom resources and third-party API groups (like Knative) are not included.
171+
*/
172+
const BUILT_IN_API_GROUPS = new Set([
173+
'core', // maps to "" (empty string) for core resources like Pod, Service, etc.
174+
'admissionregistration.k8s.io',
175+
'apiextensions.k8s.io',
176+
'apiregistration.k8s.io',
177+
'apps',
178+
'authentication.k8s.io',
179+
'authorization.k8s.io',
180+
'autoscaling',
181+
'batch',
182+
'certificates.k8s.io',
183+
'coordination.k8s.io',
184+
'discovery.k8s.io',
185+
'events.k8s.io',
186+
'flowcontrol.apiserver.k8s.io',
187+
'internal.apiserver.k8s.io',
188+
'networking.k8s.io',
189+
'node.k8s.io',
190+
'policy',
191+
'rbac.authorization.k8s.io',
192+
'resource.k8s.io',
193+
'scheduling.k8s.io',
194+
'storage.k8s.io',
195+
'storagemigration.k8s.io',
196+
]);
197+
198+
/**
199+
* Check if the given API group is a built-in Kubernetes API group.
200+
* @param group - The API group to check (e.g., "apps", "serving.knative.dev", "core")
201+
* @returns true if the group is a built-in Kubernetes API group, false otherwise
202+
*/
203+
function isBuiltInApiGroup(group: string): boolean {
204+
return BUILT_IN_API_GROUPS.has(group);
205+
}
206+
207+
export function getSerializationType(apiVersion?: string, kind?: string): string | undefined {
169208
if (apiVersion === undefined || kind === undefined) {
170209
return 'KubernetesObject';
171210
}
172211
// Types are defined in src/gen/api/models with the format "<Version><Kind>".
173212
// Version and Kind are in PascalCase.
174213
const gv = groupVersion(apiVersion);
214+
215+
// Only return a type name if this is a built-in Kubernetes API group
216+
if (!isBuiltInApiGroup(gv.group)) {
217+
return undefined;
218+
}
219+
175220
const version = gv.version.charAt(0).toUpperCase() + gv.version.slice(1);
176221
return `${version}${kind}`;
177222
}

src/util_test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,18 @@ describe('Utils', () => {
145145
});
146146

147147
it('should get the serialization type correctly', () => {
148+
// Built-in Kubernetes resources should return a type
148149
strictEqual(getSerializationType('v1', 'Pod'), 'V1Pod');
149150
strictEqual(getSerializationType('apps/v1', 'Deployment'), 'V1Deployment');
151+
strictEqual(getSerializationType('v1', 'Service'), 'V1Service');
152+
strictEqual(getSerializationType('batch/v1', 'Job'), 'V1Job');
153+
154+
// Non-built-in resources should return undefined
155+
strictEqual(getSerializationType('serving.knative.dev/v1', 'Service'), undefined);
156+
strictEqual(getSerializationType('example.com/v1', 'MyCustomResource'), undefined);
157+
strictEqual(getSerializationType('custom.io/v1alpha1', 'CustomThing'), undefined);
158+
159+
// Undefined inputs should return 'KubernetesObject'
150160
strictEqual(getSerializationType(undefined, undefined), 'KubernetesObject');
151161
});
152162
});

src/yaml.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function loadYaml<T>(data: string, opts?: yaml.LoadOptions): T {
1414
if (!yml) {
1515
throw new Error('Failed to load YAML');
1616
}
17-
const type = getSerializationType(yml.apiVersion, yml.kind);
17+
const type = getSerializationType(yml.apiVersion, yml.kind) ?? 'KubernetesObject';
1818

1919
return ObjectSerializer.deserialize(yml, type) as T;
2020
}
@@ -29,7 +29,7 @@ export function loadAllYaml(data: string, opts?: yaml.LoadOptions): any[] {
2929
const ymls = yaml.loadAll(data, undefined, opts);
3030
return ymls.map((yml) => {
3131
const obj = yml as KubernetesObject;
32-
const type = getSerializationType(obj.apiVersion, obj.kind);
32+
const type = getSerializationType(obj.apiVersion, obj.kind) ?? 'KubernetesObject';
3333
return ObjectSerializer.deserialize(yml, type);
3434
});
3535
}
@@ -42,7 +42,7 @@ export function loadAllYaml(data: string, opts?: yaml.LoadOptions): any[] {
4242
*/
4343
export function dumpYaml(object: any, opts?: yaml.DumpOptions): string {
4444
const kubeObject = object as KubernetesObject;
45-
const type = getSerializationType(kubeObject.apiVersion, kubeObject.kind);
45+
const type = getSerializationType(kubeObject.apiVersion, kubeObject.kind) ?? 'KubernetesObject';
4646
const serialized = ObjectSerializer.serialize(kubeObject, type);
4747
return yaml.dump(serialized, opts);
4848
}

src/yaml_test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,54 @@ spec:
154154
// not using strict equality as types are not matching
155155
deepEqual(actual, expected);
156156
});
157+
158+
it('should load Knative Service correctly preserving spec', () => {
159+
const yaml = `apiVersion: serving.knative.dev/v1
160+
kind: Service
161+
metadata:
162+
name: hello-world
163+
spec:
164+
template:
165+
spec:
166+
containers:
167+
- image: ghcr.io/knative/helloworld-go:latest
168+
ports:
169+
- containerPort: 8080
170+
env:
171+
- name: TARGET
172+
value: "World"`;
173+
const knativeService = loadYaml(yaml);
174+
175+
strictEqual(knativeService.apiVersion, 'serving.knative.dev/v1');
176+
strictEqual(knativeService.kind, 'Service');
177+
strictEqual(knativeService.metadata.name, 'hello-world');
178+
// Verify that the spec is preserved
179+
strictEqual(
180+
knativeService.spec.template.spec.containers[0].image,
181+
'ghcr.io/knative/helloworld-go:latest',
182+
);
183+
strictEqual(knativeService.spec.template.spec.containers[0].ports[0].containerPort, 8080);
184+
strictEqual(knativeService.spec.template.spec.containers[0].env[0].name, 'TARGET');
185+
strictEqual(knativeService.spec.template.spec.containers[0].env[0].value, 'World');
186+
});
187+
188+
it('should load custom resources correctly', () => {
189+
const yaml = `apiVersion: example.com/v1
190+
kind: MyCustomResource
191+
metadata:
192+
name: my-resource
193+
spec:
194+
customField: customValue
195+
nestedObject:
196+
key1: value1
197+
key2: value2`;
198+
const customResource = loadYaml(yaml);
199+
200+
strictEqual(customResource.apiVersion, 'example.com/v1');
201+
strictEqual(customResource.kind, 'MyCustomResource');
202+
strictEqual(customResource.metadata.name, 'my-resource');
203+
strictEqual(customResource.spec.customField, 'customValue');
204+
strictEqual(customResource.spec.nestedObject.key1, 'value1');
205+
strictEqual(customResource.spec.nestedObject.key2, 'value2');
206+
});
157207
});

0 commit comments

Comments
 (0)