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
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # Kubernetes Fluent Client for Node
2
+
3
+ [![Npm package license](https://badgen.net/npm/license/kubernetes-fluent-client)](https://npmjs.com/package/kubernetes-fluent-client)
4
+ [![Known Vulnerabilities](https://snyk.io/test/npm/kubernetes-fluent-client/badge.svg)](https://snyk.io/advisor/npm-package/kubernetes-fluent-client)
5
+ [![Npm package version](https://badgen.net/npm/v/kubernetes-fluent-client)](https://npmjs.com/package/kubernetes-fluent-client)
6
+ [![Npm package total downloads](https://badgen.net/npm/dt/kubernetes-fluent-client)](https://npmjs.com/package/kubernetes-fluent-client)
7
+
8
+ ```typescript
9
+ import { K8s, kind } from "kubernetes-fluent-client";
10
+
11
+ async function main() {
12
+ const pods = await K8s(kind.Pod).Get();
13
+ console.log(pods);
14
+ }
15
+ ```
@@ -0,0 +1,22 @@
1
+ // __mocks__/@kubernetes/client-node.ts
2
+
3
+ import { jest } from "@jest/globals";
4
+
5
+ const actual = jest.requireActual("@kubernetes/client-node") as any;
6
+
7
+ const cloned = { ...actual };
8
+
9
+ cloned.KubeConfig = class MockedKubeConfig {
10
+ loadFromDefault = jest.fn();
11
+
12
+ applyToFetchOptions = jest.fn(data => data);
13
+
14
+ getCurrentCluster() {
15
+ return {
16
+ server: "http://jest-test:8080",
17
+ };
18
+ }
19
+ };
20
+
21
+ // export all elements of the mocked module
22
+ module.exports = cloned;
@@ -0,0 +1,23 @@
1
+ /// <reference types="node" />
2
+ import { RequestInfo, RequestInit } from "node-fetch";
3
+ export type FetchResponse<T> = {
4
+ data: T;
5
+ ok: boolean;
6
+ status: number;
7
+ statusText: string;
8
+ };
9
+ /**
10
+ * Perform an async HTTP call and return the parsed JSON response, optionally
11
+ * as a specific type.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * fetch<string[]>("https://example.com/api/foo");
16
+ * ```
17
+ *
18
+ * @param url The URL or Request object to fetch
19
+ * @param init Additional options for the request
20
+ * @returns
21
+ */
22
+ export declare function fetch<T>(url: URL | RequestInfo, init?: RequestInit): Promise<FetchResponse<T>>;
23
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../src/fetch.ts"],"names":[],"mappings":";AAIA,OAAiB,EAAc,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE5E,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,EAAE,CAAC,CAAC;IACR,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAsB,KAAK,CAAC,CAAC,EAC3B,GAAG,EAAE,GAAG,GAAG,WAAW,EACtB,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CA0C3B"}
package/dist/fetch.js ADDED
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // SPDX-FileCopyrightText: 2023-Present The Pepr 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
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.fetch = void 0;
29
+ const http_status_codes_1 = require("http-status-codes");
30
+ const node_fetch_1 = __importStar(require("node-fetch"));
31
+ /**
32
+ * Perform an async HTTP call and return the parsed JSON response, optionally
33
+ * as a specific type.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * fetch<string[]>("https://example.com/api/foo");
38
+ * ```
39
+ *
40
+ * @param url The URL or Request object to fetch
41
+ * @param init Additional options for the request
42
+ * @returns
43
+ */
44
+ async function fetch(url, init) {
45
+ let data = undefined;
46
+ try {
47
+ const resp = await (0, node_fetch_1.default)(url, init);
48
+ const contentType = resp.headers.get("content-type") || "";
49
+ if (resp.ok) {
50
+ // Parse the response as JSON if the content type is JSON
51
+ if (contentType.includes("application/json")) {
52
+ data = await resp.json();
53
+ }
54
+ else {
55
+ // Otherwise, return however the response was read
56
+ data = (await resp.text());
57
+ }
58
+ }
59
+ return {
60
+ data,
61
+ ok: resp.ok,
62
+ status: resp.status,
63
+ statusText: resp.statusText,
64
+ };
65
+ }
66
+ catch (e) {
67
+ if (e instanceof node_fetch_1.FetchError) {
68
+ // Parse the error code from the FetchError or default to 400 (Bad Request)
69
+ const status = parseInt(e.code || "400");
70
+ return {
71
+ data,
72
+ ok: false,
73
+ status,
74
+ statusText: e.message,
75
+ };
76
+ }
77
+ return {
78
+ data,
79
+ ok: false,
80
+ status: http_status_codes_1.StatusCodes.BAD_REQUEST,
81
+ statusText: "Unknown error",
82
+ };
83
+ }
84
+ }
85
+ exports.fetch = fetch;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fetch.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.test.d.ts","sourceRoot":"","sources":["../src/fetch.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,97 @@
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
+ const globals_1 = require("@jest/globals");
9
+ const http_status_codes_1 = require("http-status-codes");
10
+ const nock_1 = __importDefault(require("nock"));
11
+ const fetch_1 = require("./fetch");
12
+ (0, globals_1.beforeEach)(() => {
13
+ (0, nock_1.default)("https://jsonplaceholder.typicode.com")
14
+ .get("/todos/1")
15
+ .reply(200, {
16
+ userId: 1,
17
+ id: 1,
18
+ title: "Example title",
19
+ completed: false,
20
+ })
21
+ .post("/todos", {
22
+ title: "test todo",
23
+ userId: 1,
24
+ completed: false,
25
+ })
26
+ .reply(200, (uri, requestBody) => requestBody)
27
+ .get("/todos/empty-null")
28
+ .reply(200, undefined)
29
+ .get("/todos/empty-string")
30
+ .reply(200, "")
31
+ .get("/todos/empty-object")
32
+ .reply(200, {})
33
+ .get("/todos/invalid")
34
+ .replyWithError("Something bad happened");
35
+ });
36
+ (0, globals_1.test)("fetch: should return without type data", async () => {
37
+ const url = "https://jsonplaceholder.typicode.com/todos/1";
38
+ const { data, ok } = await (0, fetch_1.fetch)(url);
39
+ (0, globals_1.expect)(ok).toBe(true);
40
+ (0, globals_1.expect)(data["title"]).toBe("Example title");
41
+ });
42
+ (0, globals_1.test)("fetch: should return parsed JSON response as a specific type", async () => {
43
+ const url = "https://jsonplaceholder.typicode.com/todos/1";
44
+ const { data, ok } = await (0, fetch_1.fetch)(url);
45
+ (0, globals_1.expect)(ok).toBe(true);
46
+ (0, globals_1.expect)(data.id).toBe(1);
47
+ (0, globals_1.expect)(typeof data.title).toBe("string");
48
+ (0, globals_1.expect)(typeof data.completed).toBe("boolean");
49
+ });
50
+ (0, globals_1.test)("fetch: should handle additional request options", async () => {
51
+ const url = "https://jsonplaceholder.typicode.com/todos";
52
+ const requestOptions = {
53
+ method: "POST",
54
+ body: JSON.stringify({
55
+ title: "test todo",
56
+ userId: 1,
57
+ completed: false,
58
+ }),
59
+ headers: {
60
+ "Content-type": "application/json; charset=UTF-8",
61
+ },
62
+ };
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const { data, ok } = await (0, fetch_1.fetch)(url, requestOptions);
65
+ (0, globals_1.expect)(ok).toBe(true);
66
+ (0, globals_1.expect)(data["title"]).toBe("test todo");
67
+ (0, globals_1.expect)(data["userId"]).toBe(1);
68
+ (0, globals_1.expect)(data["completed"]).toBe(false);
69
+ });
70
+ (0, globals_1.test)("fetch: should handle empty (null) responses", async () => {
71
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-null";
72
+ const resp = await (0, fetch_1.fetch)(url);
73
+ (0, globals_1.expect)(resp.data).toBe("");
74
+ (0, globals_1.expect)(resp.ok).toBe(true);
75
+ (0, globals_1.expect)(resp.status).toBe(http_status_codes_1.StatusCodes.OK);
76
+ });
77
+ (0, globals_1.test)("fetch: should handle empty (string) responses", async () => {
78
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-string";
79
+ const resp = await (0, fetch_1.fetch)(url);
80
+ (0, globals_1.expect)(resp.data).toBe("");
81
+ (0, globals_1.expect)(resp.ok).toBe(true);
82
+ (0, globals_1.expect)(resp.status).toBe(http_status_codes_1.StatusCodes.OK);
83
+ });
84
+ (0, globals_1.test)("fetch: should handle empty (object) responses", async () => {
85
+ const url = "https://jsonplaceholder.typicode.com/todos/empty-object";
86
+ const resp = await (0, fetch_1.fetch)(url);
87
+ (0, globals_1.expect)(resp.data).toEqual({});
88
+ (0, globals_1.expect)(resp.ok).toBe(true);
89
+ (0, globals_1.expect)(resp.status).toBe(http_status_codes_1.StatusCodes.OK);
90
+ });
91
+ (0, globals_1.test)("fetch: should handle failed requests without throwing an error", async () => {
92
+ const url = "https://jsonplaceholder.typicode.com/todos/invalid";
93
+ const resp = await (0, fetch_1.fetch)(url);
94
+ (0, globals_1.expect)(resp.data).toBe(undefined);
95
+ (0, globals_1.expect)(resp.ok).toBe(false);
96
+ (0, globals_1.expect)(resp.status).toBe(http_status_codes_1.StatusCodes.BAD_REQUEST);
97
+ });
@@ -0,0 +1,11 @@
1
+ import { KubernetesObject } from "@kubernetes/client-node";
2
+ import { GenericClass } from "../types";
3
+ import { Filters, K8sInit } from "./types";
4
+ /**
5
+ * Kubernetes fluent API inspired by Kubectl. Pass in a model, then call filters and actions on it.
6
+ *
7
+ * @param model - the model to use for the API
8
+ * @param filters - (optional) filter overrides, can also be chained
9
+ */
10
+ export declare function K8s<T extends GenericClass, K extends KubernetesObject = InstanceType<T>>(model: T, filters?: Filters): K8sInit<K>;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fluent/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKjF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAsB,MAAM,SAAS,CAAC;AAI/D;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,EACtF,KAAK,EAAE,CAAC,EACR,OAAO,GAAE,OAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CAuGZ"}
@@ -0,0 +1,100 @@
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.K8s = void 0;
6
+ const http_status_codes_1 = require("http-status-codes");
7
+ const kinds_1 = require("../kinds");
8
+ const utils_1 = require("./utils");
9
+ const watch_1 = require("./watch");
10
+ /**
11
+ * Kubernetes fluent API inspired by Kubectl. Pass in a model, then call filters and actions on it.
12
+ *
13
+ * @param model - the model to use for the API
14
+ * @param filters - (optional) filter overrides, can also be chained
15
+ */
16
+ function K8s(model, filters = {}) {
17
+ const withFilters = { WithField, WithLabel, Get, Delete, Watch };
18
+ const matchedKind = filters.kindOverride || (0, kinds_1.modelToGroupVersionKind)(model.name);
19
+ function InNamespace(namespaces) {
20
+ if (filters.namespace) {
21
+ throw new Error(`Namespace already specified: ${filters.namespace}`);
22
+ }
23
+ filters.namespace = namespaces;
24
+ return withFilters;
25
+ }
26
+ function WithField(key, value = "") {
27
+ filters.fields = filters.fields || {};
28
+ filters.fields[key] = value;
29
+ return withFilters;
30
+ }
31
+ function WithLabel(key, value = "") {
32
+ filters.labels = filters.labels || {};
33
+ filters.labels[key] = value;
34
+ return withFilters;
35
+ }
36
+ function syncFilters(payload) {
37
+ // Ensure the payload has metadata
38
+ payload.metadata = payload.metadata || {};
39
+ if (!filters.namespace) {
40
+ filters.namespace = payload.metadata.namespace;
41
+ }
42
+ if (!filters.name) {
43
+ filters.name = payload.metadata.name;
44
+ }
45
+ if (!payload.apiVersion) {
46
+ payload.apiVersion = [matchedKind.group, matchedKind.version].filter(Boolean).join("/");
47
+ }
48
+ if (!payload.kind) {
49
+ payload.kind = matchedKind.kind;
50
+ }
51
+ }
52
+ async function Get(name) {
53
+ if (name) {
54
+ if (filters.name) {
55
+ throw new Error(`Name already specified: ${filters.name}`);
56
+ }
57
+ filters.name = name;
58
+ }
59
+ return (0, utils_1.k8sExec)(model, filters, "GET");
60
+ }
61
+ async function Delete(filter) {
62
+ if (typeof filter === "string") {
63
+ filters.name = filter;
64
+ }
65
+ else if (filter) {
66
+ syncFilters(filter);
67
+ }
68
+ try {
69
+ // Try to delete the resource
70
+ await (0, utils_1.k8sExec)(model, filters, "DELETE");
71
+ }
72
+ catch (e) {
73
+ // If the resource doesn't exist, ignore the error
74
+ if (e.status === http_status_codes_1.StatusCodes.NOT_FOUND) {
75
+ return;
76
+ }
77
+ throw e;
78
+ }
79
+ }
80
+ async function Apply(resource) {
81
+ syncFilters(resource);
82
+ return (0, utils_1.k8sExec)(model, filters, "APPLY", resource);
83
+ }
84
+ async function Create(resource) {
85
+ syncFilters(resource);
86
+ return (0, utils_1.k8sExec)(model, filters, "POST", resource);
87
+ }
88
+ async function Patch(payload) {
89
+ // If there are no operations, throw an error
90
+ if (payload.length < 1) {
91
+ throw new Error("No operations specified");
92
+ }
93
+ return (0, utils_1.k8sExec)(model, filters, "PATCH", payload);
94
+ }
95
+ async function Watch(callback) {
96
+ await (0, watch_1.ExecWatch)(model, filters, callback);
97
+ }
98
+ return { InNamespace, Apply, Create, Patch, ...withFilters };
99
+ }
100
+ exports.K8s = K8s;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/fluent/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const globals_1 = require("@jest/globals");
4
+ const upstream_1 = require("../upstream");
5
+ const _1 = require(".");
6
+ const utils_1 = require("./utils");
7
+ // Setup mocks
8
+ globals_1.jest.mock("./utils");
9
+ (0, globals_1.describe)("Kube", () => {
10
+ const fakeResource = { metadata: { name: "fake", namespace: "default" } };
11
+ const mockedKubeExec = globals_1.jest.mocked(utils_1.k8sExec).mockResolvedValue(fakeResource);
12
+ (0, globals_1.beforeEach)(() => {
13
+ // Clear all instances and calls to constructor and all methods:
14
+ mockedKubeExec.mockClear();
15
+ });
16
+ (0, globals_1.it)("should create a resource", async () => {
17
+ const kube = (0, _1.K8s)(upstream_1.Pod);
18
+ const result = await kube.Create(fakeResource);
19
+ (0, globals_1.expect)(result).toEqual(fakeResource);
20
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
21
+ name: "fake",
22
+ namespace: "default",
23
+ }), "POST", fakeResource);
24
+ });
25
+ (0, globals_1.it)("should delete a resource", async () => {
26
+ const kube = (0, _1.K8s)(upstream_1.Pod);
27
+ await kube.Delete(fakeResource);
28
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
29
+ name: "fake",
30
+ namespace: "default",
31
+ }), "DELETE");
32
+ });
33
+ (0, globals_1.it)("should patch a resource", async () => {
34
+ const patchOperations = [
35
+ { op: "replace", path: "/metadata/name", value: "new-fake" },
36
+ ];
37
+ const kube = (0, _1.K8s)(upstream_1.Pod);
38
+ const result = await kube.Patch(patchOperations);
39
+ (0, globals_1.expect)(result).toEqual(fakeResource);
40
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, {}, "PATCH", patchOperations);
41
+ });
42
+ (0, globals_1.it)("should filter with WithField", async () => {
43
+ const kube = (0, _1.K8s)(upstream_1.Pod).WithField("metadata.name", "fake");
44
+ await kube.Get();
45
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
46
+ fields: {
47
+ "metadata.name": "fake",
48
+ },
49
+ }), "GET");
50
+ });
51
+ (0, globals_1.it)("should filter with WithLabel", async () => {
52
+ const kube = (0, _1.K8s)(upstream_1.Pod).WithLabel("app", "fakeApp");
53
+ await kube.Get();
54
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
55
+ labels: {
56
+ app: "fakeApp",
57
+ },
58
+ }), "GET");
59
+ });
60
+ (0, globals_1.it)("should use InNamespace", async () => {
61
+ const kube = (0, _1.K8s)(upstream_1.Pod).InNamespace("fakeNamespace");
62
+ await kube.Get();
63
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
64
+ namespace: "fakeNamespace",
65
+ }), "GET");
66
+ });
67
+ (0, globals_1.it)("should throw an error if namespace is already specified", async () => {
68
+ const kube = (0, _1.K8s)(upstream_1.Pod, { namespace: "default" });
69
+ (0, globals_1.expect)(() => kube.InNamespace("fakeNamespace")).toThrow("Namespace already specified: default");
70
+ });
71
+ (0, globals_1.it)("should handle Delete when the resource doesn't exist", async () => {
72
+ mockedKubeExec.mockRejectedValueOnce({ status: 404 }); // Not Found on first call
73
+ const kube = (0, _1.K8s)(upstream_1.Pod);
74
+ await (0, globals_1.expect)(kube.Delete("fakeResource")).resolves.toBeUndefined();
75
+ });
76
+ (0, globals_1.it)("should handle Get", async () => {
77
+ const kube = (0, _1.K8s)(upstream_1.Pod);
78
+ const result = await kube.Get("fakeResource");
79
+ (0, globals_1.expect)(result).toEqual(fakeResource);
80
+ (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
81
+ name: "fakeResource",
82
+ }), "GET");
83
+ });
84
+ (0, globals_1.it)("should thrown an error if Get is called with a name and filters are already specified a name", async () => {
85
+ const kube = (0, _1.K8s)(upstream_1.Pod, { name: "fake" });
86
+ await (0, globals_1.expect)(kube.Get("fakeResource")).rejects.toThrow("Name already specified: fake");
87
+ });
88
+ (0, globals_1.it)("should throw an error if no patch operations provided", async () => {
89
+ const kube = (0, _1.K8s)(upstream_1.Pod);
90
+ await (0, globals_1.expect)(kube.Patch([])).rejects.toThrow("No operations specified");
91
+ });
92
+ });
@@ -0,0 +1,121 @@
1
+ import { KubernetesListObject, KubernetesObject } from "@kubernetes/client-node";
2
+ import { Operation } from "fast-json-patch";
3
+ import { GenericClass, GroupVersionKind } from "../types";
4
+ /**
5
+ * The Phase matched when using the K8s Watch API.
6
+ */
7
+ export declare enum WatchPhase {
8
+ Added = "ADDED",
9
+ Modified = "MODIFIED",
10
+ Deleted = "DELETED"
11
+ }
12
+ export type FetchMethods = "GET" | "APPLY" | "POST" | "PUT" | "DELETE" | "PATCH" | "WATCH";
13
+ export interface Filters {
14
+ kindOverride?: GroupVersionKind;
15
+ fields?: Record<string, string>;
16
+ labels?: Record<string, string>;
17
+ name?: string;
18
+ namespace?: string;
19
+ }
20
+ export type GetFunction<K extends KubernetesObject> = {
21
+ (): Promise<KubernetesListObject<K>>;
22
+ (name: string): Promise<K>;
23
+ };
24
+ export type K8sFilteredActions<K extends KubernetesObject> = {
25
+ /**
26
+ * Get the resource or resources matching the filters.
27
+ * If no filters are specified, all resources will be returned.
28
+ * If a name is specified, only a single resource will be returned.
29
+ */
30
+ Get: GetFunction<K>;
31
+ /**
32
+ * Delete the resource if it exists.
33
+ *
34
+ * @param filter - the resource or resource name to delete
35
+ */
36
+ Delete: (filter?: K | string) => Promise<void>;
37
+ /**
38
+ *
39
+ * @param callback
40
+ * @returns
41
+ */
42
+ Watch: (callback: (payload: K, phase: WatchPhase) => void) => Promise<void>;
43
+ };
44
+ export type K8sUnfilteredActions<K extends KubernetesObject> = {
45
+ /**
46
+ * Perform a server-side apply of the provided K8s resource.
47
+ *
48
+ * @param resource
49
+ * @returns
50
+ */
51
+ Apply: (resource: K) => Promise<K>;
52
+ /**
53
+ * Create the provided K8s resource or throw an error if it already exists.
54
+ *
55
+ * @param resource
56
+ * @returns
57
+ */
58
+ Create: (resource: K) => Promise<K>;
59
+ /**
60
+ * Advanced JSON Patch operations for when Server Side Apply, Kube().Apply(), is insufficient.
61
+ *
62
+ * Note: Throws an error on an empty list of patch operations.
63
+ *
64
+ * @param payload The patch operations to run
65
+ * @returns The patched resource
66
+ */
67
+ Patch: (payload: Operation[]) => Promise<K>;
68
+ };
69
+ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> & {
70
+ /**
71
+ * Filter the query by the given field.
72
+ * Note multiple calls to this method will result in an AND condition. e.g.
73
+ *
74
+ * ```ts
75
+ * Kube(given.Deployment)
76
+ * .WithField("metadata.name", "bar")
77
+ * .WithField("metadata.namespace", "qux")
78
+ * .Delete(...)
79
+ * ```
80
+ *
81
+ * Will only delete the Deployment if it has the `metadata.name=bar` and `metadata.namespace=qux` fields.
82
+ *
83
+ * @param key The field key
84
+ * @param value The field value
85
+ * @returns
86
+ */
87
+ WithField: <P extends Paths<K>>(key: P, value?: string) => K8sWithFilters<K>;
88
+ /**
89
+ * Filter the query by the given label. If no value is specified, the label simply must exist.
90
+ * Note multiple calls to this method will result in an AND condition. e.g.
91
+ *
92
+ * ```ts
93
+ * Kube(given.Deployment)
94
+ * .WithLabel("foo", "bar")
95
+ * .WithLabel("baz", "qux")
96
+ * .Delete(...)
97
+ * ```
98
+ *
99
+ * Will only delete the Deployment if it has the`foo=bar` and `baz=qux` labels.
100
+ *
101
+ * @param key The label key
102
+ * @param value (optional) The label value
103
+ */
104
+ WithLabel: (key: string, value?: string) => K8sWithFilters<K>;
105
+ };
106
+ export type K8sInit<K extends KubernetesObject> = K8sWithFilters<K> & K8sUnfilteredActions<K> & {
107
+ /**
108
+ * Filter the query by the given namespace.
109
+ *
110
+ * @param namespace
111
+ * @returns
112
+ */
113
+ InNamespace: (namespace: string) => K8sWithFilters<K>;
114
+ };
115
+ export type WatchAction<T extends GenericClass, K extends KubernetesObject = InstanceType<T>> = (update: K, phase: WatchPhase) => Promise<void> | void;
116
+ type Join<K, P> = K extends string | number ? P extends string | number ? `${K}${"" extends P ? "" : "."}${P}` : never : never;
117
+ export type Paths<T, D extends number = 10> = [D] extends [never] ? never : T extends object ? {
118
+ [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, Paths<T[K]>> : never;
119
+ }[keyof T] : "";
120
+ export {};
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fluent/types.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE1D;;GAEG;AACH,oBAAY,UAAU;IACpB,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AAED,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AAE3F,MAAM,WAAW,OAAO;IACtB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,gBAAgB,IAAI;IACpD,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC3D;;;;OAIG;IACH,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IAEpB;;;;OAIG;IACH,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;;;OAIG;IACH,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,KAAK,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7E,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,gBAAgB,IAAI;IAC7D;;;;;OAKG;IACH,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAEnC;;;;;OAKG;IACH,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IAEpC;;;;;;;OAOG;IACH,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,gBAAgB,IAAI,kBAAkB,CAAC,CAAC,CAAC,GAAG;IAC/E;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC;IAE7E;;;;;;;;;;;;;;;OAeG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC;CAC/D,CAAC;AAEF,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,gBAAgB,IAAI,cAAc,CAAC,CAAC,CAAC,GACjE,oBAAoB,CAAC,CAAC,CAAC,GAAG;IACxB;;;;;OAKG;IACH,WAAW,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,cAAc,CAAC,CAAC,CAAC,CAAC;CACvD,CAAC;AAEJ,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,SAAS,gBAAgB,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,CAC9F,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,UAAU,KACd,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAG1B,KAAK,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,MAAM,GAAG,MAAM,GACvC,CAAC,SAAS,MAAM,GAAG,MAAM,GACvB,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC,EAAE,GACpC,KAAK,GACP,KAAK,CAAC;AAEV,MAAM,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC7D,KAAK,GACL,CAAC,SAAS,MAAM,GAChB;KAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK;CAAE,CAAC,MAAM,CAAC,CAAC,GAChG,EAAE,CAAC"}
@@ -0,0 +1,14 @@
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.WatchPhase = void 0;
6
+ /**
7
+ * The Phase matched when using the K8s Watch API.
8
+ */
9
+ var WatchPhase;
10
+ (function (WatchPhase) {
11
+ WatchPhase["Added"] = "ADDED";
12
+ WatchPhase["Modified"] = "MODIFIED";
13
+ WatchPhase["Deleted"] = "DELETED";
14
+ })(WatchPhase || (exports.WatchPhase = WatchPhase = {}));
@@ -0,0 +1,31 @@
1
+ /// <reference types="node" />
2
+ import { URL } from "url";
3
+ import { GenericClass } from "../types";
4
+ import { FetchMethods, Filters } from "./types";
5
+ /**
6
+ * Generate a path to a Kubernetes resource
7
+ *
8
+ * @param serverUrl
9
+ * @param model
10
+ * @param filters
11
+ * @param excludeName
12
+ * @returns
13
+ */
14
+ export declare function pathBuilder<T extends GenericClass>(serverUrl: string, model: T, filters: Filters, excludeName?: boolean): URL;
15
+ /**
16
+ * Sets up the kubeconfig and https agent for a request
17
+ *
18
+ * A few notes:
19
+ * - The kubeconfig is loaded from the default location, and can check for in-cluster config
20
+ * - We have to create an agent to handle the TLS connection (for the custom CA + mTLS in some cases)
21
+ * - The K8s lib uses request instead of node-fetch today so the object is slightly different
22
+ *
23
+ * @param method
24
+ * @returns
25
+ */
26
+ export declare function k8sCfg(method: FetchMethods): Promise<{
27
+ opts: import("node-fetch").RequestInit;
28
+ serverUrl: string;
29
+ }>;
30
+ export declare function k8sExec<T extends GenericClass, K>(model: T, filters: Filters, method: FetchMethods, payload?: K | unknown): Promise<K>;
31
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/fluent/utils.ts"],"names":[],"mappings":";AAMA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAIhD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAChD,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,WAAW,UAAQ,OAsDpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY;;;GAqBhD;AAED,wBAAsB,OAAO,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,EACrD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,cA8BtB"}