Skip to content

Commit

Permalink
add watch using genericApi
Browse files Browse the repository at this point in the history
Signed-off-by: Zahar Pecherichny <[email protected]>
  • Loading branch information
zfrhv committed Jun 4, 2024
1 parent 8a6ba36 commit d5af957
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 6 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"isomorphic-ws": "^5.0.0",
"js-yaml": "^4.1.0",
"jsonpath-plus": "^9.0.0",
"pluralize": "^8.0.0",
"request": "^2.88.0",
"rfc4648": "^1.3.0",
"stream-buffers": "^3.0.2",
Expand Down
52 changes: 52 additions & 0 deletions src/generic_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import querystring = require('querystring');
import pluralize = require('pluralize');

export interface KubernetesObject {
apiVersion: string;
kind: string;
metadata?: {
name?: string;
namespace?: string;
labels?: object;
};
}

export function CreatePath(object: KubernetesObject, include_name: boolean = true) {
if (!object.apiVersion) {
throw new Error('The object passed must contain apiVersion field')
}
if (!object.kind) {
throw new Error('The object passed must contain kind field')
}

let path = ""

// add apiVersion
if (object.apiVersion == "v1") {
path += "/api/v1"
} else {
path += "/apis/" + object.apiVersion
}

// add namespace if object namespaced
if (object.metadata?.namespace) {
path += "/namespaces/" + object.metadata.namespace
}

// add object kind
// TODO: Use discovery api to get the correct plural
path += "/" + pluralize(object.kind.toLowerCase())

// add object name
if (include_name && object.metadata?.name) {
path += "/" + object.metadata.name
}

// add label selector
const labels = object.metadata?.labels
if (labels) {
path += "?" + querystring.stringify({labelSelector: Object.keys(labels).map(label => label + "=" + labels[label]).join(",")})
}

return path
}
84 changes: 84 additions & 0 deletions src/generic_api_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect } from 'chai';
import { CreatePath } from './generic_api';



describe('CreatePath', () => {
it('should convert namespace to path', async () => {
const obj1 = {
apiVersion: "v1",
kind: "Namespace"
};

const path1 = CreatePath(obj1);
expect(path1).to.equal("/api/v1/namespaces");

const obj2 = {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: "test"
}
};

const path2 = CreatePath(obj2);
expect(path2).to.equal("/api/v1/namespaces/test");
});

it('should convert custom resource to path', async () => {
const obj1 = {
apiVersion: "fake.crd.io/v1",
kind: "fakekind",
metadata: {
name: "fake-name",
namespace: "fake-namespace"
}
};

const path1 = CreatePath(obj1);
expect(path1).to.equal("/apis/fake.crd.io/v1/namespaces/fake-namespace/fakekinds/fake-name");
});

it('should convert objects with labels to path with labels selector', async () => {
const obj1 = {
apiVersion: "v1",
kind: "pod",
metadata: {
labels: {
label1: "value1",
label2: "value2"
}
}
};

const path1 = CreatePath(obj1);
expect(path1).to.equal("/api/v1/pods?labelSelector=label1%3Dvalue1%2Clabel2%3Dvalue2");
});

it('should convert cluster wide object to path', async () => {
const obj1 = {
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "ClusterRoleBinding"
};

const path1 = CreatePath(obj1);
expect(path1).to.equal("/apis/rbac.authorization.k8s.io/v1/clusterrolebindings");
});

it('should convert object to path while skipping the name', async () => {
const obj1 = {
apiVersion: "rbac.authorization.k8s.io/v1",
kind: "RoleBinding",
metadata: {
name: "fake-name",
namespace: "fake-namespace"
}
};

const path1 = CreatePath(obj1);
expect(path1).to.equal("/apis/rbac.authorization.k8s.io/v1/namespaces/fake-namespace/rolebindings/fake-name");

const path2 = CreatePath(obj1, false);
expect(path2).to.equal("/apis/rbac.authorization.k8s.io/v1/namespaces/fake-namespace/rolebindings");
});
});
12 changes: 12 additions & 0 deletions src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import byline = require('byline');
import request = require('request');
import { Duplex } from 'stream';
import { KubeConfig } from './config';
import { KubernetesObject, CreatePath } from './generic_api'

export interface WatchUpdate {
type: string;
Expand Down Expand Up @@ -137,4 +138,15 @@ export class Watch {
req.pipe(stream);
return req;
}

// watch by object instead of url path
public async genericWatch(
object: KubernetesObject,
queryParams: any,
callback: (phase: string, apiObj: any, watchObj?: any) => void,
done: (err: any) => void,
): Promise<any> {
let path = CreatePath(object, false)
return this.watch(path, queryParams, callback, done)
}
}
29 changes: 23 additions & 6 deletions src/watch_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ describe('Watch', () => {
},
});

const path = '/some/path/to/object';
const path = '/api/v1/pods';

let doneCalled = false;
let doneErr: any;
Expand All @@ -125,6 +125,23 @@ describe('Watch', () => {
expect(doneCalled).to.equal(true);
expect(doneErr.toString()).to.equal('Error: some error');
expect(aborted).to.equal(true);

doneCalled = false;
doneErr = null;
await watch.genericWatch(
{
apiVersion: "v1",
kind: "pod"
},
{},
(phase: string, obj: string) => {},
(err: any) => {
doneCalled = true;
doneErr = err;
},
);
expect(doneCalled).to.equal(true);
expect(doneErr.toString()).to.equal('Error: some error');
});

it('should not call watch done callback more than once', async () => {
Expand Down Expand Up @@ -157,7 +174,7 @@ describe('Watch', () => {

when(fakeRequestor.webRequest(anything())).thenReturn(fakeRequest);

const path = '/some/path/to/object';
const path = '/api/v1/pods';

const receivedTypes: string[] = [];
const receivedObjects: string[] = [];
Expand Down Expand Up @@ -223,7 +240,7 @@ describe('Watch', () => {

when(fakeRequestor.webRequest(anything())).thenReturn(fakeRequest);

const path = '/some/path/to/object';
const path = '/api/v1/pods';

const receivedTypes: string[] = [];
const receivedObjects: string[] = [];
Expand Down Expand Up @@ -281,7 +298,7 @@ describe('Watch', () => {

when(fakeRequestor.webRequest(anything())).thenReturn(fakeRequest);

const path = '/some/path/to/object';
const path = '/api/v1/pods';

const receivedTypes: string[] = [];
const receivedObjects: string[] = [];
Expand Down Expand Up @@ -338,7 +355,7 @@ describe('Watch', () => {

when(fakeRequestor.webRequest(anything())).thenReturn(fakeRequest);

const path = '/some/path/to/object';
const path = '/api/v1/pods';

const receivedTypes: string[] = [];
const receivedObjects: string[] = [];
Expand Down Expand Up @@ -385,7 +402,7 @@ describe('Watch', () => {

when(fakeRequestor.webRequest(anything())).thenReturn(fakeRequest);

const path = '/some/path/to/object';
const path = '/api/v1/pods';

const receivedTypes: string[] = [];
const receivedObjects: string[] = [];
Expand Down

0 comments on commit d5af957

Please sign in to comment.