kubernetes-fluent-client 1.4.1 → 1.5.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.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Configuration for the apply function.
3
+ */
4
+ export type ApplyCfg = {
5
+ /**
6
+ * Force the apply to be a create.
7
+ */
8
+ force?: boolean;
9
+ };
10
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/fluent/apply.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB;;OAEG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC"}
@@ -0,0 +1,4 @@
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 });
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fluent/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAMjF,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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fluent/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAMjF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAsB,MAAM,SAAS,CAAC;AAK/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,CA0GZ"}
@@ -23,12 +23,12 @@ function K8s(model, filters = {}) {
23
23
  filters.namespace = namespaces;
24
24
  return withFilters;
25
25
  }
26
- function WithField(key, value = "") {
26
+ function WithField(key, value) {
27
27
  filters.fields = filters.fields || {};
28
28
  filters.fields[key] = value;
29
29
  return withFilters;
30
30
  }
31
- function WithLabel(key, value = "") {
31
+ function WithLabel(key, value) {
32
32
  filters.labels = filters.labels || {};
33
33
  filters.labels[key] = value;
34
34
  return withFilters;
@@ -77,9 +77,9 @@ function K8s(model, filters = {}) {
77
77
  throw e;
78
78
  }
79
79
  }
80
- async function Apply(resource) {
80
+ async function Apply(resource, applyCfg = { force: false }) {
81
81
  syncFilters(resource);
82
- return (0, utils_1.k8sExec)(model, filters, "APPLY", resource);
82
+ return (0, utils_1.k8sExec)(model, filters, "APPLY", resource, applyCfg);
83
83
  }
84
84
  async function Create(resource) {
85
85
  syncFilters(resource);
@@ -6,8 +6,49 @@ const _1 = require(".");
6
6
  const utils_1 = require("./utils");
7
7
  // Setup mocks
8
8
  globals_1.jest.mock("./utils");
9
+ const generateFakePodManagedFields = (manager) => {
10
+ return [
11
+ {
12
+ apiVersion: "v1",
13
+ fieldsType: "FieldsV1",
14
+ fieldsV1: {
15
+ "f:metadata": {
16
+ "f:labels": {
17
+ "f:fake": {},
18
+ },
19
+ "f:spec": {
20
+ "f:containers": {
21
+ 'k:{"name":"fake"}': {
22
+ "f:image": {},
23
+ "f:name": {},
24
+ "f:resources": {
25
+ "f:limits": {
26
+ "f:cpu": {},
27
+ "f:memory": {},
28
+ },
29
+ "f:requests": {
30
+ "f:cpu": {},
31
+ "f:memory": {},
32
+ },
33
+ },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ manager: manager,
40
+ operation: "Apply",
41
+ },
42
+ ];
43
+ };
9
44
  (0, globals_1.describe)("Kube", () => {
10
- const fakeResource = { metadata: { name: "fake", namespace: "default" } };
45
+ const fakeResource = {
46
+ metadata: {
47
+ name: "fake",
48
+ namespace: "default",
49
+ managedFields: generateFakePodManagedFields("pepr"),
50
+ },
51
+ };
11
52
  const mockedKubeExec = globals_1.jest.mocked(utils_1.k8sExec).mockResolvedValue(fakeResource);
12
53
  (0, globals_1.beforeEach)(() => {
13
54
  // Clear all instances and calls to constructor and all methods:
@@ -94,6 +135,14 @@ globals_1.jest.mock("./utils");
94
135
  const result = await kube.Apply({ metadata: { name: "fake" }, spec: { priority: 3 } });
95
136
  (0, globals_1.expect)(result).toEqual(fakeResource);
96
137
  });
138
+ (0, globals_1.it)("should allow force apply to resolve FieldManagerConflict", async () => {
139
+ const kube = (0, _1.K8s)(upstream_1.Pod);
140
+ const result = await kube.Apply({
141
+ metadata: { name: "fake", managedFields: generateFakePodManagedFields("kubectl") },
142
+ spec: { priority: 3 },
143
+ }, { force: true });
144
+ (0, globals_1.expect)(result).toEqual(fakeResource);
145
+ });
97
146
  (0, globals_1.it)("should throw an error if a Delete failed for a reason other than Not Found", async () => {
98
147
  mockedKubeExec.mockRejectedValueOnce({ status: 500 }); // Internal Server Error on first call
99
148
  const kube = (0, _1.K8s)(upstream_1.Pod);
@@ -3,6 +3,7 @@ import { Operation } from "fast-json-patch";
3
3
  import type { PartialDeep } from "type-fest";
4
4
  import { GenericClass, GroupVersionKind } from "../types";
5
5
  import { WatchCfg, WatchController } from "./watch";
6
+ import { ApplyCfg } from "./apply";
6
7
  /**
7
8
  * The Phase matched when using the K8s Watch API.
8
9
  */
@@ -50,7 +51,7 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
50
51
  * @param resource
51
52
  * @returns
52
53
  */
53
- Apply: (resource: PartialDeep<K>) => Promise<K>;
54
+ Apply: (resource: PartialDeep<K>, applyCfg?: ApplyCfg) => Promise<K>;
54
55
  /**
55
56
  * Create the provided K8s resource or throw an error if it already exists.
56
57
  *
@@ -81,12 +82,14 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
81
82
  * ```
82
83
  *
83
84
  * Will only delete the Deployment if it has the `metadata.name=bar` and `metadata.namespace=qux` fields.
85
+ * Not all fields are supported, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#supported-fields,
86
+ * but Typescript will limit to only fields that exist on the resource.
84
87
  *
85
88
  * @param key The field key
86
89
  * @param value The field value
87
90
  * @returns
88
91
  */
89
- WithField: <P extends Paths<K>>(key: P, value?: string) => K8sWithFilters<K>;
92
+ WithField: <P extends Paths<K>>(key: P, value: string) => K8sWithFilters<K>;
90
93
  /**
91
94
  * Filter the query by the given label. If no value is specified, the label simply must exist.
92
95
  * Note multiple calls to this method will result in an AND condition. e.g.
@@ -103,7 +106,7 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
103
106
  * @param key The label key
104
107
  * @param value (optional) The label value
105
108
  */
106
- WithLabel: (key: string, value?: string) => K8sWithFilters<K>;
109
+ WithLabel: (key: string, value: string) => K8sWithFilters<K>;
107
110
  };
108
111
  export type K8sInit<K extends KubernetesObject> = K8sWithFilters<K> & K8sUnfilteredActions<K> & {
109
112
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fluent/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fluent/types.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  import { URL } from "url";
3
3
  import { GenericClass } from "../types";
4
4
  import { FetchMethods, Filters } from "./types";
5
+ import { ApplyCfg } from "./apply";
5
6
  /**
6
7
  * Generate a path to a Kubernetes resource
7
8
  *
@@ -27,5 +28,5 @@ export declare function k8sCfg(method: FetchMethods): Promise<{
27
28
  opts: import("node-fetch").RequestInit;
28
29
  serverUrl: string;
29
30
  }>;
30
- export declare function k8sExec<T extends GenericClass, K>(model: T, filters: Filters, method: FetchMethods, payload?: K | unknown): Promise<K>;
31
+ export declare function k8sExec<T extends GenericClass, K>(model: T, filters: Filters, method: FetchMethods, payload?: K | unknown, applyCfg?: ApplyCfg): Promise<K>;
31
32
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +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;;;GAwBhD;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"}
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;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAInC;;;;;;;;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;;;GAwBhD;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,EACrB,QAAQ,GAAE,QAA2B,cA8BtC"}
@@ -91,7 +91,7 @@ async function k8sCfg(method) {
91
91
  return { opts, serverUrl: cluster.server };
92
92
  }
93
93
  exports.k8sCfg = k8sCfg;
94
- async function k8sExec(model, filters, method, payload) {
94
+ async function k8sExec(model, filters, method, payload, applyCfg = { force: false }) {
95
95
  const { opts, serverUrl } = await k8sCfg(method);
96
96
  const url = pathBuilder(serverUrl, model, filters, method === "POST");
97
97
  switch (opts.method) {
@@ -103,7 +103,7 @@ async function k8sExec(model, filters, method, payload) {
103
103
  opts.method = "PATCH";
104
104
  url.searchParams.set("fieldManager", "pepr");
105
105
  url.searchParams.set("fieldValidation", "Strict");
106
- url.searchParams.set("force", "false");
106
+ url.searchParams.set("force", applyCfg.force ? "true" : "false");
107
107
  break;
108
108
  }
109
109
  if (payload) {
@@ -6,6 +6,7 @@ const globals_1 = require("@jest/globals");
6
6
  const fetch_1 = require("../fetch");
7
7
  const upstream_1 = require("../upstream");
8
8
  const utils_1 = require("./utils");
9
+ const kinds_1 = require("../kinds");
9
10
  globals_1.jest.mock("https");
10
11
  globals_1.jest.mock("../fetch");
11
12
  (0, globals_1.describe)("pathBuilder Function", () => {
@@ -15,6 +16,40 @@ globals_1.jest.mock("../fetch");
15
16
  const filters = {};
16
17
  (0, globals_1.expect)(() => (0, utils_1.pathBuilder)("", model, filters)).toThrow("Kind not specified for Unknown");
17
18
  });
19
+ (0, globals_1.it)("should generate a path for core group kinds (with custom filters)", () => {
20
+ const filters = {
21
+ namespace: "default",
22
+ name: "mypod",
23
+ fields: { iamafield: "iamavalue" },
24
+ labels: { iamalabel: "iamalabelvalue" },
25
+ };
26
+ const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.Pod, filters);
27
+ const expected = new URL("/api/v1/namespaces/default/pods/mypod?fieldSelector=iamafield%3Diamavalue&labelSelector=iamalabel%3Diamalabelvalue", serverUrl);
28
+ (0, globals_1.expect)(result.toString()).toEqual(expected.toString());
29
+ });
30
+ (0, globals_1.it)("Version not specified in a Kind", () => {
31
+ const filters = {
32
+ namespace: "default",
33
+ name: "mypod",
34
+ };
35
+ class Fake {
36
+ name;
37
+ constructor() {
38
+ this.name = "Fake";
39
+ }
40
+ }
41
+ (0, kinds_1.RegisterKind)(Fake, {
42
+ kind: "Fake",
43
+ version: "",
44
+ group: "fake",
45
+ });
46
+ try {
47
+ (0, utils_1.pathBuilder)(serverUrl, Fake, filters);
48
+ }
49
+ catch (e) {
50
+ (0, globals_1.expect)(e.message).toEqual(`Version not specified for Fake`);
51
+ }
52
+ });
18
53
  (0, globals_1.it)("should generate a path for core group kinds", () => {
19
54
  const filters = { namespace: "default", name: "mypod" };
20
55
  const result = (0, utils_1.pathBuilder)(serverUrl, upstream_1.Pod, filters);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,15 +39,15 @@
39
39
  "fast-json-patch": "3.1.1",
40
40
  "http-status-codes": "2.3.0",
41
41
  "node-fetch": "2.7.0",
42
- "type-fest": "4.3.2"
42
+ "type-fest": "4.3.3"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@commitlint/cli": "17.7.2",
46
46
  "@commitlint/config-conventional": "17.7.0",
47
47
  "@jest/globals": "29.7.0",
48
48
  "@types/byline": "4.2.34",
49
- "@typescript-eslint/eslint-plugin": "6.7.3",
50
- "@typescript-eslint/parser": "6.7.3",
49
+ "@typescript-eslint/eslint-plugin": "6.7.4",
50
+ "@typescript-eslint/parser": "6.7.4",
51
51
  "jest": "29.7.0",
52
52
  "nock": "13.3.3",
53
53
  "prettier": "3.0.3",
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ /**
5
+ * Configuration for the apply function.
6
+ */
7
+ export type ApplyCfg = {
8
+ /**
9
+ * Force the apply to be a create.
10
+ */
11
+ force?: boolean;
12
+ };
@@ -8,8 +8,50 @@ import { k8sExec } from "./utils";
8
8
  // Setup mocks
9
9
  jest.mock("./utils");
10
10
 
11
+ const generateFakePodManagedFields = (manager: string) => {
12
+ return [
13
+ {
14
+ apiVersion: "v1",
15
+ fieldsType: "FieldsV1",
16
+ fieldsV1: {
17
+ "f:metadata": {
18
+ "f:labels": {
19
+ "f:fake": {},
20
+ },
21
+ "f:spec": {
22
+ "f:containers": {
23
+ 'k:{"name":"fake"}': {
24
+ "f:image": {},
25
+ "f:name": {},
26
+ "f:resources": {
27
+ "f:limits": {
28
+ "f:cpu": {},
29
+ "f:memory": {},
30
+ },
31
+ "f:requests": {
32
+ "f:cpu": {},
33
+ "f:memory": {},
34
+ },
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ },
41
+ manager: manager,
42
+ operation: "Apply",
43
+ },
44
+ ];
45
+ };
11
46
  describe("Kube", () => {
12
- const fakeResource = { metadata: { name: "fake", namespace: "default" } };
47
+ const fakeResource = {
48
+ metadata: {
49
+ name: "fake",
50
+ namespace: "default",
51
+ managedFields: generateFakePodManagedFields("pepr"),
52
+ },
53
+ };
54
+
13
55
  const mockedKubeExec = jest.mocked(k8sExec).mockResolvedValue(fakeResource);
14
56
 
15
57
  beforeEach(() => {
@@ -140,6 +182,18 @@ describe("Kube", () => {
140
182
  expect(result).toEqual(fakeResource);
141
183
  });
142
184
 
185
+ it("should allow force apply to resolve FieldManagerConflict", async () => {
186
+ const kube = K8s(Pod);
187
+ const result = await kube.Apply(
188
+ {
189
+ metadata: { name: "fake", managedFields: generateFakePodManagedFields("kubectl") },
190
+ spec: { priority: 3 },
191
+ },
192
+ { force: true },
193
+ );
194
+ expect(result).toEqual(fakeResource);
195
+ });
196
+
143
197
  it("should throw an error if a Delete failed for a reason other than Not Found", async () => {
144
198
  mockedKubeExec.mockRejectedValueOnce({ status: 500 }); // Internal Server Error on first call
145
199
  const kube = K8s(Pod);
@@ -11,6 +11,7 @@ import { GenericClass } from "../types";
11
11
  import { Filters, K8sInit, Paths, WatchAction } from "./types";
12
12
  import { k8sExec } from "./utils";
13
13
  import { ExecWatch, WatchCfg } from "./watch";
14
+ import { ApplyCfg } from "./apply";
14
15
 
15
16
  /**
16
17
  * Kubernetes fluent API inspired by Kubectl. Pass in a model, then call filters and actions on it.
@@ -34,13 +35,13 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
34
35
  return withFilters;
35
36
  }
36
37
 
37
- function WithField<P extends Paths<K>>(key: P, value = "") {
38
+ function WithField<P extends Paths<K>>(key: P, value: string) {
38
39
  filters.fields = filters.fields || {};
39
40
  filters.fields[key] = value;
40
41
  return withFilters;
41
42
  }
42
43
 
43
- function WithLabel(key: string, value = "") {
44
+ function WithLabel(key: string, value: string) {
44
45
  filters.labels = filters.labels || {};
45
46
  filters.labels[key] = value;
46
47
  return withFilters;
@@ -100,9 +101,12 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
100
101
  }
101
102
  }
102
103
 
103
- async function Apply(resource: PartialDeep<K>): Promise<K> {
104
+ async function Apply(
105
+ resource: PartialDeep<K>,
106
+ applyCfg: ApplyCfg = { force: false },
107
+ ): Promise<K> {
104
108
  syncFilters(resource as K);
105
- return k8sExec(model, filters, "APPLY", resource);
109
+ return k8sExec(model, filters, "APPLY", resource, applyCfg);
106
110
  }
107
111
 
108
112
  async function Create(resource: K): Promise<K> {
@@ -7,6 +7,7 @@ import type { PartialDeep } from "type-fest";
7
7
 
8
8
  import { GenericClass, GroupVersionKind } from "../types";
9
9
  import { WatchCfg, WatchController } from "./watch";
10
+ import { ApplyCfg } from "./apply";
10
11
 
11
12
  /**
12
13
  * The Phase matched when using the K8s Watch API.
@@ -65,7 +66,7 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
65
66
  * @param resource
66
67
  * @returns
67
68
  */
68
- Apply: (resource: PartialDeep<K>) => Promise<K>;
69
+ Apply: (resource: PartialDeep<K>, applyCfg?: ApplyCfg) => Promise<K>;
69
70
 
70
71
  /**
71
72
  * Create the provided K8s resource or throw an error if it already exists.
@@ -99,12 +100,14 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
99
100
  * ```
100
101
  *
101
102
  * Will only delete the Deployment if it has the `metadata.name=bar` and `metadata.namespace=qux` fields.
103
+ * Not all fields are supported, see https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/#supported-fields,
104
+ * but Typescript will limit to only fields that exist on the resource.
102
105
  *
103
106
  * @param key The field key
104
107
  * @param value The field value
105
108
  * @returns
106
109
  */
107
- WithField: <P extends Paths<K>>(key: P, value?: string) => K8sWithFilters<K>;
110
+ WithField: <P extends Paths<K>>(key: P, value: string) => K8sWithFilters<K>;
108
111
 
109
112
  /**
110
113
  * Filter the query by the given label. If no value is specified, the label simply must exist.
@@ -122,7 +125,7 @@ export type K8sWithFilters<K extends KubernetesObject> = K8sFilteredActions<K> &
122
125
  * @param key The label key
123
126
  * @param value (optional) The label value
124
127
  */
125
- WithLabel: (key: string, value?: string) => K8sWithFilters<K>;
128
+ WithLabel: (key: string, value: string) => K8sWithFilters<K>;
126
129
  };
127
130
 
128
131
  export type K8sInit<K extends KubernetesObject> = K8sWithFilters<K> &
@@ -8,6 +8,7 @@ import { GenericClass } from "../types";
8
8
  import { ClusterRole, Ingress, Pod } from "../upstream";
9
9
  import { Filters } from "./types";
10
10
  import { k8sExec, pathBuilder } from "./utils";
11
+ import { RegisterKind } from "../kinds";
11
12
 
12
13
  jest.mock("https");
13
14
  jest.mock("../fetch");
@@ -20,6 +21,45 @@ describe("pathBuilder Function", () => {
20
21
  expect(() => pathBuilder("", model, filters)).toThrow("Kind not specified for Unknown");
21
22
  });
22
23
 
24
+ it("should generate a path for core group kinds (with custom filters)", () => {
25
+ const filters: Filters = {
26
+ namespace: "default",
27
+ name: "mypod",
28
+ fields: { iamafield: "iamavalue" },
29
+ labels: { iamalabel: "iamalabelvalue" },
30
+ };
31
+ const result = pathBuilder(serverUrl, Pod, filters);
32
+ const expected = new URL(
33
+ "/api/v1/namespaces/default/pods/mypod?fieldSelector=iamafield%3Diamavalue&labelSelector=iamalabel%3Diamalabelvalue",
34
+ serverUrl,
35
+ );
36
+
37
+ expect(result.toString()).toEqual(expected.toString());
38
+ });
39
+
40
+ it("Version not specified in a Kind", () => {
41
+ const filters: Filters = {
42
+ namespace: "default",
43
+ name: "mypod",
44
+ };
45
+ class Fake {
46
+ name: string;
47
+ constructor() {
48
+ this.name = "Fake";
49
+ }
50
+ }
51
+ RegisterKind(Fake, {
52
+ kind: "Fake",
53
+ version: "",
54
+ group: "fake",
55
+ });
56
+ try {
57
+ pathBuilder(serverUrl, Fake, filters);
58
+ } catch (e) {
59
+ expect(e.message).toEqual(`Version not specified for Fake`);
60
+ }
61
+ });
62
+
23
63
  it("should generate a path for core group kinds", () => {
24
64
  const filters: Filters = { namespace: "default", name: "mypod" };
25
65
  const result = pathBuilder(serverUrl, Pod, filters);
@@ -9,6 +9,7 @@ import { fetch } from "../fetch";
9
9
  import { modelToGroupVersionKind } from "../kinds";
10
10
  import { GenericClass } from "../types";
11
11
  import { FetchMethods, Filters } from "./types";
12
+ import { ApplyCfg } from "./apply";
12
13
 
13
14
  const SSA_CONTENT_TYPE = "application/apply-patch+yaml";
14
15
 
@@ -123,6 +124,7 @@ export async function k8sExec<T extends GenericClass, K>(
123
124
  filters: Filters,
124
125
  method: FetchMethods,
125
126
  payload?: K | unknown,
127
+ applyCfg: ApplyCfg = { force: false },
126
128
  ) {
127
129
  const { opts, serverUrl } = await k8sCfg(method);
128
130
  const url = pathBuilder(serverUrl, model, filters, method === "POST");
@@ -137,7 +139,7 @@ export async function k8sExec<T extends GenericClass, K>(
137
139
  opts.method = "PATCH";
138
140
  url.searchParams.set("fieldManager", "pepr");
139
141
  url.searchParams.set("fieldValidation", "Strict");
140
- url.searchParams.set("force", "false");
142
+ url.searchParams.set("force", applyCfg.force ? "true" : "false");
141
143
  break;
142
144
  }
143
145