Skip to content

Commit

Permalink
JQ filtering in client
Browse files Browse the repository at this point in the history
  • Loading branch information
gergas3 committed Oct 25, 2023
1 parent 0522445 commit a8a4424
Show file tree
Hide file tree
Showing 6 changed files with 904 additions and 15 deletions.
127 changes: 127 additions & 0 deletions client/__tests__/filter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { jpFilter as jqFilter } from "client/filter";

const exampleResponse = {
headers: {
"audit-id": "6a816d03-d29f-4657-b624-35766c8a5572",
"cache-control": "no-cache, private",
"content-type": "application/json",
"x-kubernetes-pf-flowschema-uid": "ef7c3d21-4649-422d-a4fa-e5b85d52f141",
"x-kubernetes-pf-prioritylevel-uid": "a6704cd4-c4fb-48bb-982e-a1ed79bfb12d",
date: "Wed, 25 Oct 2023 06:08:30 GMT",
"transfer-encoding": "chunked",
},
status: 200,
statusText: "OK",
data: {
kind: "ClusterRoleList",
apiVersion: "rbac.authorization.k8s.io/v1",
metadata: {
resourceVersion: "4423772",
},
items: [
{
metadata: {
name: "admin",
uid: "4251609f-b3ca-4875-8ec6-76b40ab62748",
resourceVersion: "345",
creationTimestamp: "2023-10-02T22:51:23Z",
labels: {
"kubernetes.io/bootstrapping": "rbac-defaults",
},
annotations: {
"rbac.authorization.kubernetes.io/autoupdate": "true",
},
managedFields: [
{
manager: "clusterrole-aggregation-controller",
operation: "Apply",
apiVersion: "rbac.authorization.k8s.io/v1",
time: "2023-10-02T22:51:38Z",
fieldsType: "FieldsV1",
fieldsV1: {
"f:rules": {},
},
},
{
manager: "kube-apiserver",
operation: "Update",
apiVersion: "rbac.authorization.k8s.io/v1",
time: "2023-10-02T22:51:23Z",
fieldsType: "FieldsV1",
fieldsV1: {
"f:aggregationRule": {
".": {},
"f:clusterRoleSelectors": {},
},
"f:metadata": {
"f:annotations": {
".": {},
"f:rbac.authorization.kubernetes.io/autoupdate": {},
},
"f:labels": {
".": {},
"f:kubernetes.io/bootstrapping": {},
},
},
},
},
],
},
},
{
metadata: {
name: "aws-node",
uid: "63a3bed8-a750-440d-bca9-b5f41c8786c2",
resourceVersion: "273",
creationTimestamp: "2023-10-02T22:51:35Z",
labels: {
"app.kubernetes.io/instance": "aws-vpc-cni",
"app.kubernetes.io/name": "aws-node",
"app.kubernetes.io/version": "v1.12.6",
"k8s-app": "aws-node",
},
annotations: {
"kubectl.kubernetes.io/last-applied-configuration":
'{"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRole","metadata":{"annotations":{},"labels":{"app.kubernetes.io/instance":"aws-vpc-cni","app.kubernetes.io/name":"aws-node","app.kubernetes.io/version":"v1.12.6","k8s-app":"aws-node"},"name":"aws-node"},"rules":[{"apiGroups":["crd.k8s.amazonaws.com"],"resources":["eniconfigs"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["namespaces"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["pods"],"verbs":["list","watch","get"]},{"apiGroups":[""],"resources":["nodes"],"verbs":["list","watch","get","update"]},{"apiGroups":["extensions"],"resources":["*"],"verbs":["list","watch"]},{"apiGroups":["","events.k8s.io"],"resources":["events"],"verbs":["create","patch","list"]}]}\n',
},
managedFields: [
{
manager: "kubectl-client-side-apply",
operation: "Update",
apiVersion: "rbac.authorization.k8s.io/v1",
time: "2023-10-02T22:51:35Z",
fieldsType: "FieldsV1",
fieldsV1: {
"f:metadata": {
"f:annotations": {
".": {},
"f:kubectl.kubernetes.io/last-applied-configuration": {},
},
"f:labels": {
".": {},
"f:app.kubernetes.io/instance": {},
"f:app.kubernetes.io/name": {},
"f:app.kubernetes.io/version": {},
"f:k8s-app": {},
},
},
"f:rules": {},
},
},
],
},
},
],
},
};

describe("json path filter", () => {
it("returns only selected fields", async () => {
const result = await jqFilter(
exampleResponse,
"{ headers: .headers, status: .status, statusText: .statusText, data: { kind: .data.kind, items: [.data.items[] | { metadata: { name: .metadata.name } }] } }"
);

expect(result).toEqual(["admin", "aws-node"]);
});
});
20 changes: 20 additions & 0 deletions client/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { isArray } from "lodash";

// jq-node doesn't support `import` syntax
const jq = require("node-jq");

export const jpFilter = async (
data: any,
jqHeader: string | string[] | undefined
): Promise<any> => {
const jpSelectQuery = isArray(jqHeader) ? jqHeader[0] : jqHeader;
if (!jpSelectQuery) {
return data;
}
try {
return await jq.run(jpSelectQuery, data, { input: "json", output: "json" });
} catch (e: any) {
console.log(e);
return data;
}
};
13 changes: 9 additions & 4 deletions client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import {
JSONRPCServer,
JSONRPCServerAndClient,
} from "json-rpc-2.0";
import { omit } from "lodash";
import { isArray, omit } from "lodash";
import { Logger } from "pino";
import WebSocket from "ws";

import { DEFAULT_FORWARDED_REQUEST_TIMEOUT_MILLIS } from "../common/constants";
import { createLogger } from "../log";
import { ForwardedRequest, ForwardedResponse } from "../types";
import { ForwardedRequest, ForwardedResponse, JQ_HEADER } from "../types";
import { deferral } from "../util/deferral";
import { Backoff } from "./backoff";
import { jpFilter as jqFilter } from "./filter";
import { jwt } from "./jwks";

/**
Expand Down Expand Up @@ -62,7 +63,7 @@ export class JsonRpcClient {
});

axios.interceptors.response.use((response) => {
// Do not log response object, it's lengthy and difficult to filter out the authorization header
// Do not log request object, it's lengthy and difficult to filter out the authorization header
this.#logger.debug(
{ response: omit(response, "request") },
"Axios response"
Expand Down Expand Up @@ -153,11 +154,15 @@ export class JsonRpcClient {
request.options?.timeoutMillis ||
DEFAULT_FORWARDED_REQUEST_TIMEOUT_MILLIS,
});
const jpSelectHeader = request.headers[JQ_HEADER];
this.#logger.debug({ response }, "forwarded response before filters");
const data = await jqFilter(response.data, jpSelectHeader);
this.#logger.debug({ data }, "forwarded response data after filters");
return {
headers: response.headers,
status: response.status,
statusText: response.statusText,
data: response.data,
data,
} as ForwardedResponse;
});

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"json-rpc-2.0": "^1.6.0",
"json-stream-stringify": "^3.1.0",
"lodash": "^4.17.21",
"node-jq": "^4.2.2",
"path-to-regexp": "^6.2.1",
"pino": "^8.14.1",
"pino-http": "^8.3.3",
Expand Down
2 changes: 2 additions & 0 deletions types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Request } from "express";
import core from "express-serve-static-core";
import { IncomingHttpHeaders } from "node:http";

export const JQ_HEADER = "x-braekhus-jq";

export type IncomingRequest = Request<
core.ParamsDictionary,
any,
Expand Down
Loading

0 comments on commit a8a4424

Please sign in to comment.