kubernetes-fluent-client 0.0.0-development → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +15 -0
  2. package/__mocks__/@kubernetes/client-node.ts +22 -0
  3. package/dist/fetch.d.ts +23 -0
  4. package/dist/fetch.d.ts.map +1 -0
  5. package/dist/fetch.js +85 -0
  6. package/dist/fetch.test.d.ts +2 -0
  7. package/dist/fetch.test.d.ts.map +1 -0
  8. package/dist/fetch.test.js +97 -0
  9. package/dist/fluent/index.d.ts +11 -0
  10. package/dist/fluent/index.d.ts.map +1 -0
  11. package/dist/fluent/index.js +100 -0
  12. package/dist/fluent/index.test.d.ts +2 -0
  13. package/dist/fluent/index.test.d.ts.map +1 -0
  14. package/dist/fluent/index.test.js +92 -0
  15. package/dist/fluent/types.d.ts +121 -0
  16. package/dist/fluent/types.d.ts.map +1 -0
  17. package/dist/fluent/types.js +14 -0
  18. package/dist/fluent/utils.d.ts +31 -0
  19. package/dist/fluent/utils.d.ts.map +1 -0
  20. package/dist/fluent/utils.js +116 -0
  21. package/dist/fluent/utils.test.d.ts +2 -0
  22. package/dist/fluent/utils.test.d.ts.map +1 -0
  23. package/dist/fluent/utils.test.js +88 -0
  24. package/dist/fluent/watch.d.ts +8 -0
  25. package/dist/fluent/watch.d.ts.map +1 -0
  26. package/dist/fluent/watch.js +78 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +44 -1
  30. package/dist/kinds.d.ts +10 -0
  31. package/dist/kinds.d.ts.map +1 -0
  32. package/dist/kinds.js +489 -0
  33. package/dist/kinds.test.d.ts +2 -0
  34. package/dist/kinds.test.d.ts.map +1 -0
  35. package/dist/kinds.test.js +142 -0
  36. package/dist/types.d.ts +26 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +16 -0
  39. package/dist/upstream.d.ts +4 -0
  40. package/dist/upstream.d.ts.map +1 -0
  41. package/dist/upstream.js +54 -0
  42. package/package.json +21 -8
  43. package/src/fetch.test.ts +115 -0
  44. package/src/fetch.ts +72 -0
  45. package/src/fluent/index.test.ts +136 -0
  46. package/src/fluent/index.ts +126 -0
  47. package/src/fluent/types.ts +151 -0
  48. package/src/fluent/utils.test.ts +110 -0
  49. package/src/fluent/utils.ts +152 -0
  50. package/src/fluent/watch.ts +94 -0
  51. package/src/index.ts +8 -3
  52. package/src/kinds.test.ts +1 -1
  53. package/src/kinds.ts +1 -1
  54. package/src/types.ts +1 -154
  55. package/.eslintrc.json +0 -26
  56. package/.prettierrc +0 -13
  57. package/coverage/clover.xml +0 -83
  58. package/coverage/coverage-final.json +0 -5
  59. package/coverage/lcov-report/base.css +0 -224
  60. package/coverage/lcov-report/block-navigation.js +0 -87
  61. package/coverage/lcov-report/favicon.png +0 -0
  62. package/coverage/lcov-report/index.html +0 -161
  63. package/coverage/lcov-report/index.ts.html +0 -127
  64. package/coverage/lcov-report/kinds.ts.html +0 -1675
  65. package/coverage/lcov-report/prettify.css +0 -1
  66. package/coverage/lcov-report/prettify.js +0 -2
  67. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  68. package/coverage/lcov-report/sorter.js +0 -196
  69. package/coverage/lcov-report/types.ts.html +0 -643
  70. package/coverage/lcov-report/upstream.ts.html +0 -244
  71. package/coverage/lcov.info +0 -208
  72. package/jest.config.json +0 -4
  73. package/tsconfig.json +0 -18
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.k8sExec = exports.k8sCfg = exports.pathBuilder = void 0;
6
+ const client_node_1 = require("@kubernetes/client-node");
7
+ const url_1 = require("url");
8
+ const fetch_1 = require("../fetch");
9
+ const kinds_1 = require("../kinds");
10
+ const SSA_CONTENT_TYPE = "application/apply-patch+yaml";
11
+ /**
12
+ * Generate a path to a Kubernetes resource
13
+ *
14
+ * @param serverUrl
15
+ * @param model
16
+ * @param filters
17
+ * @param excludeName
18
+ * @returns
19
+ */
20
+ function pathBuilder(serverUrl, model, filters, excludeName = false) {
21
+ const matchedKind = filters.kindOverride || (0, kinds_1.modelToGroupVersionKind)(model.name);
22
+ // If the kind is not specified and the model is not a KubernetesObject, throw an error
23
+ if (!matchedKind) {
24
+ throw new Error(`Kind not specified for ${model.name}`);
25
+ }
26
+ // Use the plural property if it exists, otherwise use lowercase kind + s
27
+ const plural = matchedKind.plural || `${matchedKind.kind.toLowerCase()}s`;
28
+ let base = "/api/v1";
29
+ // If the kind is not in the core group, add the group and version to the path
30
+ if (matchedKind.group) {
31
+ if (!matchedKind.version) {
32
+ throw new Error(`Version not specified for ${model.name}`);
33
+ }
34
+ base = `/apis/${matchedKind.group}/${matchedKind.version}`;
35
+ }
36
+ // Namespaced paths require a namespace prefix
37
+ const namespace = filters.namespace ? `namespaces/${filters.namespace}` : "";
38
+ // Name should not be included in some paths
39
+ const name = excludeName ? "" : filters.name;
40
+ // Build the complete path to the resource
41
+ const path = [base, namespace, plural, name].filter(Boolean).join("/");
42
+ // Generate the URL object
43
+ const url = new url_1.URL(path, serverUrl);
44
+ // Add field selectors to the query params
45
+ if (filters.fields) {
46
+ const fieldSelector = Object.entries(filters.fields)
47
+ .map(([key, value]) => `${key}=${value}`)
48
+ .join(",");
49
+ url.searchParams.set("fieldSelector", fieldSelector);
50
+ }
51
+ // Add label selectors to the query params
52
+ if (filters.labels) {
53
+ const labelSelector = Object.entries(filters.labels)
54
+ .map(([key, value]) => `${key}=${value}`)
55
+ .join(",");
56
+ url.searchParams.set("labelSelector", labelSelector);
57
+ }
58
+ return url;
59
+ }
60
+ exports.pathBuilder = pathBuilder;
61
+ /**
62
+ * Sets up the kubeconfig and https agent for a request
63
+ *
64
+ * A few notes:
65
+ * - The kubeconfig is loaded from the default location, and can check for in-cluster config
66
+ * - We have to create an agent to handle the TLS connection (for the custom CA + mTLS in some cases)
67
+ * - The K8s lib uses request instead of node-fetch today so the object is slightly different
68
+ *
69
+ * @param method
70
+ * @returns
71
+ */
72
+ async function k8sCfg(method) {
73
+ const kubeConfig = new client_node_1.KubeConfig();
74
+ kubeConfig.loadFromDefault();
75
+ const cluster = kubeConfig.getCurrentCluster();
76
+ if (!cluster) {
77
+ throw new Error("No currently active cluster");
78
+ }
79
+ // Setup the TLS options & auth headers, as needed
80
+ const opts = await kubeConfig.applyToFetchOptions({
81
+ method,
82
+ headers: {
83
+ // Set the default content type to JSON
84
+ "Content-Type": "application/json",
85
+ // Set the user agent like kubectl does
86
+ "User-Agent": `kubernetes-fluent-client`,
87
+ },
88
+ });
89
+ return { opts, serverUrl: cluster.server };
90
+ }
91
+ exports.k8sCfg = k8sCfg;
92
+ async function k8sExec(model, filters, method, payload) {
93
+ const { opts, serverUrl } = await k8sCfg(method);
94
+ const url = pathBuilder(serverUrl, model, filters, method === "POST");
95
+ switch (opts.method) {
96
+ case "PATCH":
97
+ opts.headers.set("Content-Type", client_node_1.PatchStrategy.JsonPatch);
98
+ break;
99
+ case "APPLY":
100
+ opts.headers.set("Content-Type", SSA_CONTENT_TYPE);
101
+ opts.method = "PATCH";
102
+ url.searchParams.set("fieldManager", "pepr");
103
+ url.searchParams.set("fieldValidation", "Strict");
104
+ url.searchParams.set("force", "false");
105
+ break;
106
+ }
107
+ if (payload) {
108
+ opts.body = JSON.stringify(payload);
109
+ }
110
+ const resp = await (0, fetch_1.fetch)(url, opts);
111
+ if (resp.ok) {
112
+ return resp.data;
113
+ }
114
+ throw resp;
115
+ }
116
+ exports.k8sExec = k8sExec;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../src/fluent/utils.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ const globals_1 = require("@jest/globals");
6
+ const fetch_1 = require("../fetch");
7
+ const upstream_1 = require("../upstream");
8
+ const utils_1 = require("./utils");
9
+ globals_1.jest.mock("https");
10
+ globals_1.jest.mock("../fetch");
11
+ (0, globals_1.describe)("pathBuilder Function", () => {
12
+ const serverUrl = "https://jest-test:8080";
13
+ (0, globals_1.it)("should throw an error if the kind is not specified and the model is not a KubernetesObject", () => {
14
+ const model = { name: "Unknown" };
15
+ const filters = {};
16
+ (0, globals_1.expect)(() => (0, utils_1.pathBuilder)("", model, filters)).toThrow("Kind not specified for Unknown");
17
+ });
18
+ (0, globals_1.it)("should generate a path for core group kinds", () => {
19
+ const filters = { namespace: "default", name: "mypod" };
20
+ const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.Pod, filters);
21
+ const expected = new URL("/api/v1/namespaces/default/pods/mypod", serverUrl);
22
+ (0, globals_1.expect)(result).toEqual(expected);
23
+ });
24
+ (0, globals_1.it)("should generate a path for non-core group kinds", () => {
25
+ const filters = {
26
+ namespace: "default",
27
+ name: "myingress",
28
+ };
29
+ const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.Ingress, filters);
30
+ const expected = new URL("/apis/networking.k8s.io/v1/namespaces/default/ingresses/myingress", serverUrl);
31
+ (0, globals_1.expect)(result).toEqual(expected);
32
+ });
33
+ (0, globals_1.it)("should generate a path without a namespace if not provided", () => {
34
+ const filters = { name: "tester" };
35
+ const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.ClusterRole, filters);
36
+ const expected = new URL("/apis/rbac.authorization.k8s.io/v1/clusterroles/tester", serverUrl);
37
+ (0, globals_1.expect)(result).toEqual(expected);
38
+ });
39
+ (0, globals_1.it)("should generate a path without a name if excludeName is true", () => {
40
+ const filters = { namespace: "default", name: "mypod" };
41
+ const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.Pod, filters, true);
42
+ const expected = new URL("/api/v1/namespaces/default/pods", serverUrl);
43
+ (0, globals_1.expect)(result).toEqual(expected);
44
+ });
45
+ });
46
+ (0, globals_1.describe)("kubeExec Function", () => {
47
+ const mockedFetch = globals_1.jest.mocked(fetch_1.fetch);
48
+ const fakeFilters = { name: "fake", namespace: "default" };
49
+ const fakeMethod = "GET";
50
+ const fakePayload = { metadata: { name: "fake", namespace: "default" } };
51
+ const fakeUrl = new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake");
52
+ const fakeOpts = {
53
+ body: JSON.stringify(fakePayload),
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ "User-Agent": `kubernetes-fluent-client`,
57
+ },
58
+ method: fakeMethod,
59
+ };
60
+ (0, globals_1.beforeEach)(() => {
61
+ mockedFetch.mockClear();
62
+ });
63
+ (0, globals_1.it)("should make a successful fetch call", async () => {
64
+ mockedFetch.mockResolvedValueOnce({
65
+ ok: true,
66
+ data: fakePayload,
67
+ status: 200,
68
+ statusText: "OK",
69
+ });
70
+ const result = await (0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, fakeMethod, fakePayload);
71
+ (0, globals_1.expect)(result).toEqual(fakePayload);
72
+ (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(fakeUrl, globals_1.expect.objectContaining(fakeOpts));
73
+ });
74
+ (0, globals_1.it)("should handle fetch call failure", async () => {
75
+ const fakeStatus = 404;
76
+ const fakeStatusText = "Not Found";
77
+ mockedFetch.mockResolvedValueOnce({
78
+ ok: false,
79
+ data: null,
80
+ status: fakeStatus,
81
+ statusText: fakeStatusText,
82
+ });
83
+ await (0, globals_1.expect)((0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, fakeMethod, fakePayload)).rejects.toEqual(globals_1.expect.objectContaining({
84
+ status: fakeStatus,
85
+ statusText: fakeStatusText,
86
+ }));
87
+ });
88
+ });
@@ -0,0 +1,8 @@
1
+ /// <reference types="node" />
2
+ import { GenericClass } from "../types";
3
+ import { Filters, WatchAction } from "./types";
4
+ /**
5
+ * Execute a watch on the specified resource.
6
+ */
7
+ export declare function ExecWatch<T extends GenericClass>(model: T, filters: Filters, callback: WatchAction<T>): Promise<AbortController>;
8
+ //# sourceMappingURL=watch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/fluent/watch.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAc,MAAM,SAAS,CAAC;AAG3D;;GAEG;AACH,wBAAsB,SAAS,CAAC,CAAC,SAAS,YAAY,EACpD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,4BA6EzB"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ExecWatch = void 0;
9
+ const readline_1 = __importDefault(require("readline"));
10
+ const node_fetch_1 = __importDefault(require("node-fetch"));
11
+ const utils_1 = require("./utils");
12
+ /**
13
+ * Execute a watch on the specified resource.
14
+ */
15
+ async function ExecWatch(model, filters, callback) {
16
+ // Build the path and query params for the resource, excluding the name
17
+ const { opts, serverUrl } = await (0, utils_1.k8sCfg)("GET");
18
+ const url = (0, utils_1.pathBuilder)(serverUrl, model, filters, true);
19
+ // Enable the watch query param
20
+ url.searchParams.set("watch", "true");
21
+ // Allow bookmarks to be used for the watch
22
+ url.searchParams.set("allowWatchBookmarks", "true");
23
+ // If a name is specified, add it to the query params
24
+ if (filters.name) {
25
+ url.searchParams.set("fieldSelector", `metadata.name=${filters.name}`);
26
+ }
27
+ // Add abort controller to the long-running request
28
+ const controller = new AbortController();
29
+ opts.signal = controller.signal;
30
+ // Close the connection and make the callback function no-op
31
+ let close = (err) => {
32
+ controller.abort();
33
+ close = () => { };
34
+ if (err) {
35
+ throw err;
36
+ }
37
+ };
38
+ try {
39
+ // Make the actual request
40
+ const response = await (0, node_fetch_1.default)(url, opts);
41
+ // If the request is successful, start listening for events
42
+ if (response.ok) {
43
+ const { body } = response;
44
+ // Bind connection events to the close function
45
+ body.on("error", close);
46
+ body.on("close", close);
47
+ body.on("finish", close);
48
+ // Create a readline interface to parse the stream
49
+ const rl = readline_1.default.createInterface({
50
+ input: response.body,
51
+ terminal: false,
52
+ });
53
+ // Listen for events and call the callback function
54
+ rl.on("line", line => {
55
+ try {
56
+ // Parse the event payload
57
+ const { object: payload, type: phase } = JSON.parse(line);
58
+ // Call the callback function with the parsed payload
59
+ void callback(payload, phase);
60
+ }
61
+ catch (ignore) {
62
+ // ignore parse errors
63
+ }
64
+ });
65
+ }
66
+ else {
67
+ // If the request fails, throw an error
68
+ const error = new Error(response.statusText);
69
+ error.statusCode = response.status;
70
+ throw error;
71
+ }
72
+ }
73
+ catch (e) {
74
+ close(e);
75
+ }
76
+ return controller;
77
+ }
78
+ exports.ExecWatch = ExecWatch;
package/dist/index.d.ts CHANGED
@@ -1 +1,8 @@
1
+ import * as kind from "./upstream";
2
+ /** kind is a collection of K8s types to be used within a K8s call: `K8s(kind.Secret).Apply({})`. */
3
+ export { kind };
4
+ export { fetch } from "./fetch";
5
+ export { K8s } from "./fluent";
6
+ export { RegisterKind, modelToGroupVersionKind } from "./kinds";
7
+ export * from "./types";
1
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,IAAI,MAAM,YAAY,CAAC;AAEnC,oGAAoG;AACpG,OAAO,EAAE,IAAI,EAAE,CAAC;AAGhB,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AAEhE,cAAc,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,45 @@
1
1
  "use strict";
2
- console.log("Hello, world!");
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
28
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.modelToGroupVersionKind = exports.RegisterKind = exports.K8s = exports.fetch = exports.kind = void 0;
32
+ // Export kinds as a single object
33
+ const kind = __importStar(require("./upstream"));
34
+ exports.kind = kind;
35
+ // Export the node-fetch wrapper
36
+ var fetch_1 = require("./fetch");
37
+ Object.defineProperty(exports, "fetch", { enumerable: true, get: function () { return fetch_1.fetch; } });
38
+ // Export the fluent API entrypoint
39
+ var fluent_1 = require("./fluent");
40
+ Object.defineProperty(exports, "K8s", { enumerable: true, get: function () { return fluent_1.K8s; } });
41
+ // Export helpers for working with K8s types
42
+ var kinds_1 = require("./kinds");
43
+ Object.defineProperty(exports, "RegisterKind", { enumerable: true, get: function () { return kinds_1.RegisterKind; } });
44
+ Object.defineProperty(exports, "modelToGroupVersionKind", { enumerable: true, get: function () { return kinds_1.modelToGroupVersionKind; } });
45
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,10 @@
1
+ import { GenericClass, GroupVersionKind } from "./types";
2
+ export declare function modelToGroupVersionKind(key: string): GroupVersionKind;
3
+ /**
4
+ * Registers a new model and GroupVersionKind to be used within the fluent API.
5
+ *
6
+ * @param model Used to match the GroupVersionKind and define the type-data for the request
7
+ * @param groupVersionKind Contains the match parameters to determine the request should be handled
8
+ */
9
+ export declare const RegisterKind: (model: GenericClass, groupVersionKind: GroupVersionKind) => void;
10
+ //# sourceMappingURL=kinds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kinds.d.ts","sourceRoot":"","sources":["../src/kinds.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AA0fzD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAErE;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,UAAW,YAAY,oBAAoB,gBAAgB,SAUnF,CAAC"}