kubernetes-fluent-client 3.6.3 → 3.6.4

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.
@@ -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;AAOjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAS,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAuC,MAAM,mBAAmB,CAAC;AAKjF;;;;;;GAMG;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,EAAE,CAAC,CAAC,CAyRf"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fluent/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwB,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAOjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAS,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAuC,MAAM,mBAAmB,CAAC;AAKjF;;;;;;GAMG;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,EAAE,CAAC,CAAC,CAgSf"}
@@ -92,7 +92,7 @@ export function K8s(model, filters = {}) {
92
92
  throw new Error("Kind must be Pod or have a selector");
93
93
  }
94
94
  try {
95
- const object = await k8sExec(model, filters, FetchMethods.GET);
95
+ const object = await k8sExec(model, filters, { method: FetchMethods.GET });
96
96
  if (kind !== "Pod") {
97
97
  if (kind === "Service") {
98
98
  const svc = object;
@@ -118,7 +118,7 @@ export function K8s(model, filters = {}) {
118
118
  throw new Error(`Failed to get logs in KFC Logs function`);
119
119
  }
120
120
  const podModel = { ...model, name: "V1Pod" };
121
- const logPromises = podList.map(po => k8sExec(podModel, { ...filters, name: po.metadata.name }, FetchMethods.LOG));
121
+ const logPromises = podList.map(po => k8sExec(podModel, { ...filters, name: po.metadata.name }, { method: FetchMethods.LOG }));
122
122
  const responses = await Promise.all(logPromises);
123
123
  const combinedString = responses.reduce((accumulator, currentString, i) => {
124
124
  const prefixedLines = currentString
@@ -142,7 +142,7 @@ export function K8s(model, filters = {}) {
142
142
  }
143
143
  filters.name = name;
144
144
  }
145
- return k8sExec(model, filters, FetchMethods.GET);
145
+ return k8sExec(model, filters, { method: FetchMethods.GET });
146
146
  }
147
147
  /**
148
148
  * @inheritdoc
@@ -157,7 +157,7 @@ export function K8s(model, filters = {}) {
157
157
  }
158
158
  try {
159
159
  // Try to delete the resource
160
- await k8sExec(model, filters, FetchMethods.DELETE);
160
+ await k8sExec(model, filters, { method: FetchMethods.DELETE });
161
161
  }
162
162
  catch (e) {
163
163
  // If the resource doesn't exist, ignore the error
@@ -173,7 +173,7 @@ export function K8s(model, filters = {}) {
173
173
  */
174
174
  async function Apply(resource, applyCfg = { force: false }) {
175
175
  syncFilters(resource);
176
- return k8sExec(model, filters, FetchMethods.APPLY, resource, applyCfg);
176
+ return k8sExec(model, filters, { method: FetchMethods.APPLY, payload: resource }, applyCfg);
177
177
  }
178
178
  /**
179
179
  * @inheritdoc
@@ -181,7 +181,7 @@ export function K8s(model, filters = {}) {
181
181
  */
182
182
  async function Create(resource) {
183
183
  syncFilters(resource);
184
- return k8sExec(model, filters, FetchMethods.POST, resource);
184
+ return k8sExec(model, filters, { method: FetchMethods.POST, payload: resource });
185
185
  }
186
186
  /**
187
187
  * @inheritdoc
@@ -204,7 +204,10 @@ export function K8s(model, filters = {}) {
204
204
  },
205
205
  };
206
206
  // Try to evict the resource
207
- await k8sExec(model, filters, FetchMethods.POST, evictionPayload);
207
+ await k8sExec(model, filters, {
208
+ method: FetchMethods.POST,
209
+ payload: evictionPayload,
210
+ });
208
211
  }
209
212
  catch (e) {
210
213
  // If the resource doesn't exist, ignore the error
@@ -223,7 +226,7 @@ export function K8s(model, filters = {}) {
223
226
  if (payload.length < 1) {
224
227
  throw new Error("No operations specified");
225
228
  }
226
- return k8sExec(model, filters, FetchMethods.PATCH, payload);
229
+ return k8sExec(model, filters, { method: FetchMethods.PATCH, payload });
227
230
  }
228
231
  /**
229
232
  * @inheritdoc
@@ -231,7 +234,7 @@ export function K8s(model, filters = {}) {
231
234
  */
232
235
  async function PatchStatus(resource) {
233
236
  syncFilters(resource);
234
- return k8sExec(model, filters, FetchMethods.PATCH_STATUS, resource);
237
+ return k8sExec(model, filters, { method: FetchMethods.PATCH_STATUS, payload: resource });
235
238
  }
236
239
  /**
237
240
  * @inheritdoc
@@ -45,16 +45,43 @@ export declare function pathBuilder<T extends GenericClass>(serverUrl: string, m
45
45
  * @returns the fetch options and server URL
46
46
  */
47
47
  export declare function k8sCfg(method: FetchMethods): K8sConfigPromise;
48
+ /**
49
+ * Prepares and mutates the request options and URL for Kubernetes PATCH or APPLY operations.
50
+ *
51
+ * This function modifies the request's HTTP method, headers, and URL based on the operation type.
52
+ * It handles the following:
53
+ *
54
+ * - `PATCH_STATUS`: Converts the method to `PATCH`, appends `/status` to the path, sets merge patch headers,
55
+ * and rewrites the payload to contain only the `status` field.
56
+ * - `PATCH`: Sets the content type to `application/json-patch+json`.
57
+ * - `APPLY`: Converts the method to `PATCH`, sets server-side apply headers, and updates the query string
58
+ * with field manager and force options.
59
+ *
60
+ * @template K
61
+ * @param methodPayload - The original method and payload. May be mutated if `PATCH_STATUS` is used.
62
+ * @param opts - The request options.
63
+ * @param opts.method - The HTTP method (e.g. `PATCH`, `APPLY`, or `PATCH_STATUS`).
64
+ * @param opts.headers - The headers to be updated with the correct content type.
65
+ * @param url - The URL to mutate with subresource path or query parameters.
66
+ * @param applyCfg - Server-side apply options, such as `force`.
67
+ */
68
+ export declare function prepareRequestOptions<K>(methodPayload: MethodPayload<K>, opts: {
69
+ method?: string;
70
+ headers?: Record<string, string>;
71
+ }, url: URL, applyCfg: ApplyCfg): void;
72
+ export type MethodPayload<K> = {
73
+ method: FetchMethods;
74
+ payload?: K | unknown;
75
+ };
48
76
  /**
49
77
  * Execute a request against the Kubernetes API server.
50
78
  *
51
79
  * @param model - the model to use for the API
52
80
  * @param filters - (optional) filter overrides, can also be chained
53
- * @param method - the HTTP method to use
54
- * @param payload - (optional) the payload to send
81
+ * @param methodPayload - method and payload for the request
55
82
  * @param applyCfg - (optional) configuration for the apply method
56
83
  *
57
84
  * @returns the parsed JSON response
58
85
  */
59
- export declare function k8sExec<T extends GenericClass, K>(model: T, filters: Filters, method: FetchMethods, payload?: K | unknown, applyCfg?: ApplyCfg): Promise<K>;
86
+ export declare function k8sExec<T extends GenericClass, K>(model: T, filters: Filters, methodPayload: MethodPayload<K>, applyCfg?: ApplyCfg): Promise<K>;
60
87
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/fluent/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAS,UAAU,EAAE,MAAM,QAAQ,CAAC;AAI3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMtF;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBvF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,CAqBvE;AACD;;;;GAIG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMvD;AACD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAChD,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,WAAW,UAAQ,OAwDpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CA+BnE;AASD;;;;;;;;;;GAUG;AACH,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,cA+DtC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/fluent/utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAS,UAAU,EAAE,MAAM,QAAQ,CAAC;AAI3C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAMtF;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAkBvF;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,SAAS,CAqBvE;AACD;;;;GAIG;AACH,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMvD;AACD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,YAAY,EAChD,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,WAAW,UAAQ,OAwDpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,gBAAgB,CA+BnE;AASD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACrC,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,EAC/B,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,EAC3D,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,QAAQ,GACjB,IAAI,CAsBN;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;CACvB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAsB,OAAO,CAAC,CAAC,SAAS,YAAY,EAAE,CAAC,EACrD,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,OAAO,EAChB,aAAa,EAAE,aAAa,CAAC,CAAC,CAAC,EAC/B,QAAQ,GAAE,QAA2B,cAkDtC"}
@@ -164,29 +164,70 @@ const isEvictionPayload = (payload) => payload !== null &&
164
164
  typeof payload === "object" &&
165
165
  "kind" in payload &&
166
166
  payload.kind === "Eviction";
167
+ /**
168
+ * Prepares and mutates the request options and URL for Kubernetes PATCH or APPLY operations.
169
+ *
170
+ * This function modifies the request's HTTP method, headers, and URL based on the operation type.
171
+ * It handles the following:
172
+ *
173
+ * - `PATCH_STATUS`: Converts the method to `PATCH`, appends `/status` to the path, sets merge patch headers,
174
+ * and rewrites the payload to contain only the `status` field.
175
+ * - `PATCH`: Sets the content type to `application/json-patch+json`.
176
+ * - `APPLY`: Converts the method to `PATCH`, sets server-side apply headers, and updates the query string
177
+ * with field manager and force options.
178
+ *
179
+ * @template K
180
+ * @param methodPayload - The original method and payload. May be mutated if `PATCH_STATUS` is used.
181
+ * @param opts - The request options.
182
+ * @param opts.method - The HTTP method (e.g. `PATCH`, `APPLY`, or `PATCH_STATUS`).
183
+ * @param opts.headers - The headers to be updated with the correct content type.
184
+ * @param url - The URL to mutate with subresource path or query parameters.
185
+ * @param applyCfg - Server-side apply options, such as `force`.
186
+ */
187
+ export function prepareRequestOptions(methodPayload, opts, url, applyCfg) {
188
+ switch (opts.method) {
189
+ // PATCH_STATUS is a special case that uses the PATCH method on status subresources
190
+ case "PATCH_STATUS":
191
+ opts.method = "PATCH";
192
+ url.pathname = `${url.pathname}/status`;
193
+ opts.headers["Content-Type"] = PatchStrategy.MergePatch;
194
+ methodPayload.payload = { status: methodPayload.payload.status };
195
+ break;
196
+ case "PATCH":
197
+ opts.headers["Content-Type"] = PatchStrategy.JsonPatch;
198
+ break;
199
+ case "APPLY":
200
+ opts.headers["Content-Type"] = SSA_CONTENT_TYPE;
201
+ opts.method = "PATCH";
202
+ url.searchParams.set("fieldManager", "pepr");
203
+ url.searchParams.set("fieldValidation", "Strict");
204
+ url.searchParams.set("force", applyCfg.force ? "true" : "false");
205
+ break;
206
+ }
207
+ }
167
208
  /**
168
209
  * Execute a request against the Kubernetes API server.
169
210
  *
170
211
  * @param model - the model to use for the API
171
212
  * @param filters - (optional) filter overrides, can also be chained
172
- * @param method - the HTTP method to use
173
- * @param payload - (optional) the payload to send
213
+ * @param methodPayload - method and payload for the request
174
214
  * @param applyCfg - (optional) configuration for the apply method
175
215
  *
176
216
  * @returns the parsed JSON response
177
217
  */
178
- export async function k8sExec(model, filters, method, payload, applyCfg = { force: false }) {
218
+ export async function k8sExec(model, filters, methodPayload, applyCfg = { force: false }) {
179
219
  const reconstruct = async (method) => {
180
220
  const configMethod = method === FetchMethods.LOG ? FetchMethods.GET : method;
181
221
  const { opts, serverUrl } = await k8sCfg(configMethod);
182
222
  // Build the base path once, using excludeName only for standard POST requests
183
- const shouldExcludeName = method === "POST" && !(payload && isEvictionPayload(payload));
223
+ const shouldExcludeName = method === FetchMethods.POST &&
224
+ !(methodPayload.payload && isEvictionPayload(methodPayload.payload));
184
225
  const baseUrl = pathBuilder(serverUrl.toString(), model, filters, shouldExcludeName);
185
226
  // Append appropriate subresource paths
186
- if (payload && isEvictionPayload(payload)) {
227
+ if (methodPayload.payload && isEvictionPayload(methodPayload.payload)) {
187
228
  baseUrl.pathname = `${baseUrl.pathname}/eviction`;
188
229
  }
189
- else if (method === "LOG") {
230
+ else if (method === FetchMethods.LOG) {
190
231
  baseUrl.pathname = `${baseUrl.pathname}/log`;
191
232
  }
192
233
  return {
@@ -194,35 +235,17 @@ export async function k8sExec(model, filters, method, payload, applyCfg = { forc
194
235
  opts,
195
236
  };
196
237
  };
197
- const { opts, serverUrl } = await reconstruct(method);
238
+ const { opts, serverUrl } = await reconstruct(methodPayload.method);
198
239
  const url = serverUrl instanceof URL ? serverUrl : new URL(serverUrl);
199
- switch (opts.method) {
200
- // PATCH_STATUS is a special case that uses the PATCH method on status subresources
201
- case "PATCH_STATUS":
202
- opts.method = "PATCH";
203
- url.pathname = `${url.pathname}/status`;
204
- opts.headers["Content-Type"] = PatchStrategy.MergePatch;
205
- payload = { status: payload.status };
206
- break;
207
- case "PATCH":
208
- opts.headers["Content-Type"] = PatchStrategy.JsonPatch;
209
- break;
210
- case "APPLY":
211
- opts.headers["Content-Type"] = SSA_CONTENT_TYPE;
212
- opts.method = "PATCH";
213
- url.searchParams.set("fieldManager", "pepr");
214
- url.searchParams.set("fieldValidation", "Strict");
215
- url.searchParams.set("force", applyCfg.force ? "true" : "false");
216
- break;
217
- }
218
- if (payload) {
219
- opts.body = JSON.stringify(payload);
240
+ prepareRequestOptions(methodPayload, opts, url, applyCfg);
241
+ if (methodPayload.payload) {
242
+ opts.body = JSON.stringify(methodPayload.payload);
220
243
  }
221
244
  const resp = await fetch(url, opts);
222
245
  if (resp.ok) {
223
246
  return resp.data;
224
247
  }
225
- if (resp.status === 404 && method === "PATCH_STATUS") {
248
+ if (resp.status === 404 && methodPayload.method === FetchMethods.PATCH_STATUS) {
226
249
  resp.statusText =
227
250
  "Not Found" + " (NOTE: This error is expected if the resource has no status subresource)";
228
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "3.6.3",
3
+ "version": "3.6.4",
4
4
  "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply.",
5
5
  "bin": "./dist/cli.js",
6
6
  "main": "dist/index.js",
@@ -70,15 +70,15 @@
70
70
  "@types/urijs": "^1.19.25",
71
71
  "@types/ws": "^8.18.1",
72
72
  "@types/yargs": "17.0.33",
73
- "@typescript-eslint/eslint-plugin": "8.33.1",
74
- "@typescript-eslint/parser": "8.33.1",
73
+ "@typescript-eslint/eslint-plugin": "8.34.1",
74
+ "@typescript-eslint/parser": "8.34.1",
75
75
  "@vitest/coverage-v8": "^3.2.1",
76
76
  "command-line-args": "^6.0.1",
77
- "eslint-plugin-jsdoc": "50.7.1",
77
+ "eslint-plugin-jsdoc": "51.2.1",
78
78
  "globals": "^16.0.0",
79
79
  "husky": "^9.1.6",
80
80
  "lint-staged": "^16.0.0",
81
- "prettier": "3.5.3",
81
+ "prettier": "3.6.0",
82
82
  "semantic-release": "24.2.5",
83
83
  "typescript": "5.8.3",
84
84
  "vitest": "^3.2.1"
@@ -115,7 +115,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
115
115
  }
116
116
 
117
117
  try {
118
- const object = await k8sExec<T, K>(model, filters, FetchMethods.GET);
118
+ const object = await k8sExec<T, K>(model, filters, { method: FetchMethods.GET });
119
119
 
120
120
  if (kind !== "Pod") {
121
121
  if (kind === "Service") {
@@ -145,7 +145,11 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
145
145
 
146
146
  const podModel = { ...model, name: "V1Pod" };
147
147
  const logPromises = podList.map(po =>
148
- k8sExec<T, string>(podModel, { ...filters, name: po.metadata!.name! }, FetchMethods.LOG),
148
+ k8sExec<T, string>(
149
+ podModel,
150
+ { ...filters, name: po.metadata!.name! },
151
+ { method: FetchMethods.LOG },
152
+ ),
149
153
  );
150
154
 
151
155
  const responses = await Promise.all(logPromises);
@@ -180,7 +184,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
180
184
  filters.name = name;
181
185
  }
182
186
 
183
- return k8sExec<T, K | KubernetesListObject<K>>(model, filters, FetchMethods.GET);
187
+ return k8sExec<T, K | KubernetesListObject<K>>(model, filters, { method: FetchMethods.GET });
184
188
  }
185
189
 
186
190
  /**
@@ -196,7 +200,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
196
200
 
197
201
  try {
198
202
  // Try to delete the resource
199
- await k8sExec<T, void>(model, filters, FetchMethods.DELETE);
203
+ await k8sExec<T, void>(model, filters, { method: FetchMethods.DELETE });
200
204
  } catch (e) {
201
205
  // If the resource doesn't exist, ignore the error
202
206
  if (e.status === StatusCodes.NOT_FOUND) {
@@ -216,7 +220,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
216
220
  applyCfg: ApplyCfg = { force: false },
217
221
  ): Promise<K> {
218
222
  syncFilters(resource as K);
219
- return k8sExec(model, filters, FetchMethods.APPLY, resource, applyCfg);
223
+ return k8sExec(model, filters, { method: FetchMethods.APPLY, payload: resource }, applyCfg);
220
224
  }
221
225
 
222
226
  /**
@@ -225,7 +229,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
225
229
  */
226
230
  async function Create(resource: K): Promise<K> {
227
231
  syncFilters(resource);
228
- return k8sExec(model, filters, FetchMethods.POST, resource);
232
+ return k8sExec(model, filters, { method: FetchMethods.POST, payload: resource });
229
233
  }
230
234
 
231
235
  /**
@@ -249,7 +253,10 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
249
253
  },
250
254
  };
251
255
  // Try to evict the resource
252
- await k8sExec<T, void>(model, filters, FetchMethods.POST, evictionPayload);
256
+ await k8sExec<T, void>(model, filters, {
257
+ method: FetchMethods.POST,
258
+ payload: evictionPayload,
259
+ });
253
260
  } catch (e) {
254
261
  // If the resource doesn't exist, ignore the error
255
262
  if (e.status === StatusCodes.NOT_FOUND) {
@@ -269,7 +276,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
269
276
  throw new Error("No operations specified");
270
277
  }
271
278
 
272
- return k8sExec(model, filters, FetchMethods.PATCH, payload);
279
+ return k8sExec(model, filters, { method: FetchMethods.PATCH, payload });
273
280
  }
274
281
 
275
282
  /**
@@ -278,7 +285,7 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
278
285
  */
279
286
  async function PatchStatus(resource: PartialDeep<K>): Promise<K> {
280
287
  syncFilters(resource as K);
281
- return k8sExec(model, filters, FetchMethods.PATCH_STATUS, resource);
288
+ return k8sExec(model, filters, { method: FetchMethods.PATCH_STATUS, payload: resource });
282
289
  }
283
290
 
284
291
  /**
@@ -203,13 +203,66 @@ const isEvictionPayload = (payload: unknown): payload is Eviction =>
203
203
  "kind" in payload &&
204
204
  (payload as { kind: string }).kind === "Eviction";
205
205
 
206
+ /**
207
+ * Prepares and mutates the request options and URL for Kubernetes PATCH or APPLY operations.
208
+ *
209
+ * This function modifies the request's HTTP method, headers, and URL based on the operation type.
210
+ * It handles the following:
211
+ *
212
+ * - `PATCH_STATUS`: Converts the method to `PATCH`, appends `/status` to the path, sets merge patch headers,
213
+ * and rewrites the payload to contain only the `status` field.
214
+ * - `PATCH`: Sets the content type to `application/json-patch+json`.
215
+ * - `APPLY`: Converts the method to `PATCH`, sets server-side apply headers, and updates the query string
216
+ * with field manager and force options.
217
+ *
218
+ * @template K
219
+ * @param methodPayload - The original method and payload. May be mutated if `PATCH_STATUS` is used.
220
+ * @param opts - The request options.
221
+ * @param opts.method - The HTTP method (e.g. `PATCH`, `APPLY`, or `PATCH_STATUS`).
222
+ * @param opts.headers - The headers to be updated with the correct content type.
223
+ * @param url - The URL to mutate with subresource path or query parameters.
224
+ * @param applyCfg - Server-side apply options, such as `force`.
225
+ */
226
+ export function prepareRequestOptions<K>(
227
+ methodPayload: MethodPayload<K>,
228
+ opts: { method?: string; headers?: Record<string, string> },
229
+ url: URL,
230
+ applyCfg: ApplyCfg,
231
+ ): void {
232
+ switch (opts.method) {
233
+ // PATCH_STATUS is a special case that uses the PATCH method on status subresources
234
+ case "PATCH_STATUS":
235
+ opts.method = "PATCH";
236
+ url.pathname = `${url.pathname}/status`;
237
+ (opts.headers as Record<string, string>)["Content-Type"] = PatchStrategy.MergePatch;
238
+ methodPayload.payload = { status: (methodPayload.payload as { status: unknown }).status };
239
+ break;
240
+
241
+ case "PATCH":
242
+ (opts.headers as Record<string, string>)["Content-Type"] = PatchStrategy.JsonPatch;
243
+ break;
244
+
245
+ case "APPLY":
246
+ (opts.headers as Record<string, string>)["Content-Type"] = SSA_CONTENT_TYPE;
247
+ opts.method = "PATCH";
248
+ url.searchParams.set("fieldManager", "pepr");
249
+ url.searchParams.set("fieldValidation", "Strict");
250
+ url.searchParams.set("force", applyCfg.force ? "true" : "false");
251
+ break;
252
+ }
253
+ }
254
+
255
+ export type MethodPayload<K> = {
256
+ method: FetchMethods;
257
+ payload?: K | unknown;
258
+ };
259
+
206
260
  /**
207
261
  * Execute a request against the Kubernetes API server.
208
262
  *
209
263
  * @param model - the model to use for the API
210
264
  * @param filters - (optional) filter overrides, can also be chained
211
- * @param method - the HTTP method to use
212
- * @param payload - (optional) the payload to send
265
+ * @param methodPayload - method and payload for the request
213
266
  * @param applyCfg - (optional) configuration for the apply method
214
267
  *
215
268
  * @returns the parsed JSON response
@@ -217,8 +270,7 @@ const isEvictionPayload = (payload: unknown): payload is Eviction =>
217
270
  export async function k8sExec<T extends GenericClass, K>(
218
271
  model: T,
219
272
  filters: Filters,
220
- method: FetchMethods,
221
- payload?: K | unknown,
273
+ methodPayload: MethodPayload<K>,
222
274
  applyCfg: ApplyCfg = { force: false },
223
275
  ) {
224
276
  const reconstruct = async (method: FetchMethods): K8sConfigPromise => {
@@ -226,13 +278,15 @@ export async function k8sExec<T extends GenericClass, K>(
226
278
  const { opts, serverUrl } = await k8sCfg(configMethod);
227
279
 
228
280
  // Build the base path once, using excludeName only for standard POST requests
229
- const shouldExcludeName = method === "POST" && !(payload && isEvictionPayload(payload));
281
+ const shouldExcludeName =
282
+ method === FetchMethods.POST &&
283
+ !(methodPayload.payload && isEvictionPayload(methodPayload.payload));
230
284
  const baseUrl = pathBuilder(serverUrl.toString(), model, filters, shouldExcludeName);
231
285
 
232
286
  // Append appropriate subresource paths
233
- if (payload && isEvictionPayload(payload)) {
287
+ if (methodPayload.payload && isEvictionPayload(methodPayload.payload)) {
234
288
  baseUrl.pathname = `${baseUrl.pathname}/eviction`;
235
- } else if (method === "LOG") {
289
+ } else if (method === FetchMethods.LOG) {
236
290
  baseUrl.pathname = `${baseUrl.pathname}/log`;
237
291
  }
238
292
 
@@ -242,33 +296,18 @@ export async function k8sExec<T extends GenericClass, K>(
242
296
  };
243
297
  };
244
298
 
245
- const { opts, serverUrl } = await reconstruct(method);
299
+ const { opts, serverUrl } = await reconstruct(methodPayload.method);
246
300
  const url: URL = serverUrl instanceof URL ? serverUrl : new URL(serverUrl);
247
301
 
248
- switch (opts.method) {
249
- // PATCH_STATUS is a special case that uses the PATCH method on status subresources
250
- case "PATCH_STATUS":
251
- opts.method = "PATCH";
252
- url.pathname = `${url.pathname}/status`;
253
- (opts.headers as Record<string, string>)["Content-Type"] = PatchStrategy.MergePatch;
254
- payload = { status: (payload as { status: unknown }).status };
255
- break;
256
-
257
- case "PATCH":
258
- (opts.headers as Record<string, string>)["Content-Type"] = PatchStrategy.JsonPatch;
259
- break;
260
-
261
- case "APPLY":
262
- (opts.headers as Record<string, string>)["Content-Type"] = SSA_CONTENT_TYPE;
263
- opts.method = "PATCH";
264
- url.searchParams.set("fieldManager", "pepr");
265
- url.searchParams.set("fieldValidation", "Strict");
266
- url.searchParams.set("force", applyCfg.force ? "true" : "false");
267
- break;
268
- }
302
+ prepareRequestOptions(
303
+ methodPayload,
304
+ opts as { method?: string; headers?: Record<string, string> },
305
+ url,
306
+ applyCfg,
307
+ );
269
308
 
270
- if (payload) {
271
- opts.body = JSON.stringify(payload);
309
+ if (methodPayload.payload) {
310
+ opts.body = JSON.stringify(methodPayload.payload);
272
311
  }
273
312
  const resp = await fetch<K>(url, opts);
274
313
 
@@ -276,7 +315,7 @@ export async function k8sExec<T extends GenericClass, K>(
276
315
  return resp.data;
277
316
  }
278
317
 
279
- if (resp.status === 404 && method === "PATCH_STATUS") {
318
+ if (resp.status === 404 && methodPayload.method === FetchMethods.PATCH_STATUS) {
280
319
  resp.statusText =
281
320
  "Not Found" + " (NOTE: This error is expected if the resource has no status subresource)";
282
321
  }