Skip to content
Merged
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
45 changes: 45 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,58 @@ 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';
}
// Types are defined in src/gen/api/models with the format "<Version><Kind>".
// 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}`;
}
Expand Down
12 changes: 12 additions & 0 deletions src/util_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
51 changes: 51 additions & 0 deletions src/yaml_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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');
});
});