From 608ee8ba8eb666f0a0f6312d9b4c56c7efe9781a Mon Sep 17 00:00:00 2001 From: schrodit Date: Tue, 29 Apr 2025 18:35:07 +0200 Subject: [PATCH 1/2] fix(object): properly serialize objects on create/replace/patch --- src/object.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/object.ts b/src/object.ts index 52c98a7dec..0548b03a04 100644 --- a/src/object.ts +++ b/src/object.ts @@ -107,12 +107,13 @@ export class KubernetesObjectApi { if (fieldManager !== undefined) { requestContext.setQueryParam('fieldManager', ObjectSerializer.serialize(fieldManager, 'string')); } + const type = await this.getSerializationType(spec.apiVersion, spec.kind); // Body Params const contentType = ObjectSerializer.getPreferredMediaType([]); requestContext.setHeaderParam('Content-Type', contentType); const serializedBody = ObjectSerializer.stringify( - ObjectSerializer.serialize(spec, 'any'), + ObjectSerializer.serialize(spec, type), contentType, ); requestContext.setBody(serializedBody); @@ -268,9 +269,11 @@ export class KubernetesObjectApi { requestContext.setQueryParam('force', ObjectSerializer.serialize(force, 'boolean')); } + const type = await this.getSerializationType(spec.apiVersion, spec.kind); + // Body Params const serializedBody = ObjectSerializer.stringify( - ObjectSerializer.serialize(spec, 'any'), + ObjectSerializer.serialize(spec, type), // TODO: use the patch content type once ObjectSerializer supports it. 'application/json', ); @@ -465,11 +468,13 @@ export class KubernetesObjectApi { requestContext.setQueryParam('fieldManager', ObjectSerializer.serialize(fieldManager, 'string')); } + const type = await this.getSerializationType(spec.apiVersion, spec.kind); + // Body Params const contentType = ObjectSerializer.getPreferredMediaType([]); requestContext.setHeaderParam('Content-Type', contentType); const serializedBody = ObjectSerializer.stringify( - ObjectSerializer.serialize(spec, 'any'), + ObjectSerializer.serialize(spec, type), contentType, ); requestContext.setBody(serializedBody); From 39c35d9412c172d6527f2b96ba8777bc89486f84 Mon Sep 17 00:00:00 2001 From: schrodit Date: Wed, 30 Apr 2025 17:27:06 +0200 Subject: [PATCH 2/2] add tests --- src/object_test.ts | 122 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/src/object_test.ts b/src/object_test.ts index 2ccb298dcd..e27f77edf2 100644 --- a/src/object_test.ts +++ b/src/object_test.ts @@ -1071,6 +1071,9 @@ describe('KubernetesObject', () => { kc.loadFromOptions(testConfigOptions); client = KubernetesObjectApi.makeApiClient(kc); (client as any).apiVersionResourceCache.v1 = JSON.parse(resourceBodies.core); + (client as any).apiVersionResourceCache['networking.k8s.io/v1'] = JSON.parse( + resourceBodies.networking, + ); }); it('should modify resources with defaults', async () => { @@ -1520,6 +1523,125 @@ describe('KubernetesObject', () => { } }); + it('should properly serialize resources on modify', async () => { + const netPol = { + apiVersion: 'networking.k8s.io/v1', + kind: 'NetworkPolicy', + metadata: { + name: 'k8s-js-client-test', + namespace: 'default', + }, + spec: { + podSelector: { + matchLabels: { + app: 'my-app', + }, + }, + policyTypes: ['Ingress'], + ingress: [ + { + _from: [ + { + podSelector: { matchLabels: { app: 'foo' } }, + }, + ], + ports: [{ port: 123 }], + }, + ], + }, + }; + const serializedNetPol = { + apiVersion: 'networking.k8s.io/v1', + kind: 'NetworkPolicy', + metadata: { + name: 'k8s-js-client-test', + namespace: 'default', + }, + spec: { + podSelector: { + matchLabels: { + app: 'my-app', + }, + }, + policyTypes: ['Ingress'], + ingress: [ + { + from: [ + { + podSelector: { matchLabels: { app: 'foo' } }, + }, + ], + ports: [{ port: 123 }], + }, + ], + }, + }; + const returnBody = `{ + "kind": "NetworkPolicy", + "apiVersion": "networking.k8s.io/v1", + "metadata": { + "name": "k8s-js-client-test", + "namespace": "default", + "selfLink": "/api/v1/namespaces/default/services/k8s-js-client-test", + "uid": "6a43eddc-26bf-424e-ab30-cde3041a706a", + "resourceVersion": "32373", + "creationTimestamp": "2020-05-11T17:34:25Z" + }, + "spec": { + "policyTypes": ["Ingress"], + "podSelector": { + "matchLabels": { + "app": "my-app" + } + }, + "ingress": [ + { + "from": [{ + "podSelector": { + "matchLabels": { + "app": "foo" + } + } + }], + "ports": [{"port": 123}] + } + ] + } +}`; + const methods = [ + { + m: client.create, + v: 'POST', + p: '/apis/networking.k8s.io/v1/namespaces/default/networkpolicies', + c: 201, + b: returnBody, + }, + { + m: client.replace, + v: 'PUT', + p: '/apis/networking.k8s.io/v1/namespaces/default/networkpolicies/k8s-js-client-test', + c: 200, + b: returnBody, + }, + { + m: client.patch, + v: 'PATCH', + p: '/apis/networking.k8s.io/v1/namespaces/default/networkpolicies/k8s-js-client-test', + c: 200, + b: returnBody, + }, + ]; + for (const m of methods) { + const scope = nock('https://d.i.y') + .intercept(m.p, m.v, serializedNetPol) + .reply(m.c, m.b, contentTypeJsonHeader); + // TODO: Figure out why Typescript barfs if we do m.call + const hack_m = m.m as any; + await hack_m.call(client, netPol); + scope.done(); + } + }); + it('should replace a resource', async () => { const s = { apiVersion: 'v1',