diff --git a/src/util.ts b/src/util.ts index e502e600d9..fe6a0a3558 100644 --- a/src/util.ts +++ b/src/util.ts @@ -165,6 +165,45 @@ export function normalizeResponseHeaders(response: Response): { [key: string]: s return normalizedHeaders; } +/** + * Built-in Kubernetes API groups that have generated TypeScript models. + * Custom resources and third-party API groups (like Knative) are not included. + */ +const BUILT_IN_API_GROUPS = new Set([ + 'core', // maps to "" (empty string) for core resources like Pod, Service, etc. + 'admissionregistration.k8s.io', + 'apiextensions.k8s.io', + 'apiregistration.k8s.io', + 'apps', + 'authentication.k8s.io', + 'authorization.k8s.io', + 'autoscaling', + 'batch', + 'certificates.k8s.io', + 'coordination.k8s.io', + 'discovery.k8s.io', + 'events.k8s.io', + 'flowcontrol.apiserver.k8s.io', + 'internal.apiserver.k8s.io', + 'networking.k8s.io', + 'node.k8s.io', + 'policy', + 'rbac.authorization.k8s.io', + 'resource.k8s.io', + 'scheduling.k8s.io', + 'storage.k8s.io', + 'storagemigration.k8s.io', +]); + +/** + * Check if the given API group is a built-in Kubernetes API group. + * @param group - The API group to check (e.g., "apps", "serving.knative.dev", "core") + * @returns true if the group is a built-in Kubernetes API group, false otherwise + */ +function isBuiltInApiGroup(group: string): boolean { + return BUILT_IN_API_GROUPS.has(group); +} + export function getSerializationType(apiVersion?: string, kind?: string): string { if (apiVersion === undefined || kind === undefined) { return 'KubernetesObject'; @@ -172,6 +211,12 @@ export function getSerializationType(apiVersion?: string, kind?: string): string // Types are defined in src/gen/api/models with the format "". // Version and Kind are in PascalCase. const gv = groupVersion(apiVersion); + + // Only return a type name if this is a built-in Kubernetes API group + if (!isBuiltInApiGroup(gv.group)) { + return 'KubernetesObject'; + } + const version = gv.version.charAt(0).toUpperCase() + gv.version.slice(1); return `${version}${kind}`; } diff --git a/src/util_test.ts b/src/util_test.ts index 74a323a866..a932b7bbd0 100644 --- a/src/util_test.ts +++ b/src/util_test.ts @@ -147,6 +147,18 @@ describe('Utils', () => { it('should get the serialization type correctly', () => { strictEqual(getSerializationType('v1', 'Pod'), 'V1Pod'); strictEqual(getSerializationType('apps/v1', 'Deployment'), 'V1Deployment'); + // Built-in Kubernetes resources should return a type + strictEqual(getSerializationType('v1', 'Pod'), 'V1Pod'); + strictEqual(getSerializationType('apps/v1', 'Deployment'), 'V1Deployment'); + strictEqual(getSerializationType('v1', 'Service'), 'V1Service'); + strictEqual(getSerializationType('batch/v1', 'Job'), 'V1Job'); + + // Non-built-in resources should return 'KubernetesObject' + strictEqual(getSerializationType('serving.knative.dev/v1', 'Service'), 'KubernetesObject'); + strictEqual(getSerializationType('example.com/v1', 'MyCustomResource'), 'KubernetesObject'); + strictEqual(getSerializationType('custom.io/v1alpha1', 'CustomThing'), 'KubernetesObject'); + + // Undefined inputs should return 'KubernetesObject' strictEqual(getSerializationType(undefined, undefined), 'KubernetesObject'); }); }); diff --git a/src/yaml_test.ts b/src/yaml_test.ts index 958f7ba45b..0174393a94 100644 --- a/src/yaml_test.ts +++ b/src/yaml_test.ts @@ -2,6 +2,7 @@ import { describe, it } from 'node:test'; import { deepEqual, deepStrictEqual, strictEqual } from 'node:assert'; import { V1CustomResourceDefinition, V1Namespace } from './api.js'; import { dumpYaml, loadAllYaml, loadYaml } from './yaml.js'; +import { KubernetesObject } from './types.js'; describe('yaml', () => { it('should load safely', () => { @@ -154,4 +155,54 @@ spec: // not using strict equality as types are not matching deepEqual(actual, expected); }); + + it('should load Knative Service correctly preserving spec', () => { + const yaml = `apiVersion: serving.knative.dev/v1 +kind: Service +metadata: + name: hello-world +spec: + template: + spec: + containers: + - image: ghcr.io/knative/helloworld-go:latest + ports: + - containerPort: 8080 + env: + - name: TARGET + value: "World"`; + const knativeService = loadYaml(yaml) as KubernetesObject; + + strictEqual(knativeService.apiVersion, 'serving.knative.dev/v1'); + strictEqual(knativeService.kind, 'Service'); + strictEqual((knativeService as any).metadata.name, 'hello-world'); + // Verify that the spec is preserved + strictEqual( + (knativeService as any).spec.template.spec.containers[0].image, + 'ghcr.io/knative/helloworld-go:latest', + ); + strictEqual((knativeService as any).spec.template.spec.containers[0].ports[0].containerPort, 8080); + strictEqual((knativeService as any).spec.template.spec.containers[0].env[0].name, 'TARGET'); + strictEqual((knativeService as any).spec.template.spec.containers[0].env[0].value, 'World'); + }); + + it('should load custom resources correctly', () => { + const yaml = `apiVersion: example.com/v1 +kind: MyCustomResource +metadata: + name: my-resource +spec: + customField: customValue + nestedObject: + key1: value1 + key2: value2`; + const customResource = loadYaml(yaml) as KubernetesObject; + + strictEqual((customResource as any).apiVersion, 'example.com/v1'); + strictEqual((customResource as any).kind, 'MyCustomResource'); + strictEqual((customResource as any).metadata.name, 'my-resource'); + strictEqual((customResource as any).spec.customField, 'customValue'); + strictEqual((customResource as any).spec.nestedObject.key1, 'value1'); + strictEqual((customResource as any).spec.nestedObject.key2, 'value2'); + }); });