kubernetes-fluent-client 2.0.1 → 2.1.3

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.
package/CODEOWNERS ADDED
@@ -0,0 +1,7 @@
1
+ * @jeff-mccoy @cmwylie19 @btlghrants @schaeferka
2
+
3
+ # Additional privileged files
4
+ /CODEOWNERS @jeff-mccoy @austenbryan
5
+ /cosign.pub @jeff-mccoy @austenbryan
6
+ /LICENSE @jeff-mccoy @austenbryan
7
+
@@ -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,UAAU,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAsB,MAAM,SAAS,CAAC;AAI/D;;;;;;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,CAmKf"}
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,UAAU,CAAC;AACxC,OAAO,EAA0B,OAAO,EAAE,OAAO,EAAsB,MAAM,SAAS,CAAC;AAIvF;;;;;;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,CA4Kf"}
@@ -131,6 +131,14 @@ function K8s(model, filters = {}) {
131
131
  }
132
132
  return (0, utils_1.k8sExec)(model, filters, "PATCH", payload);
133
133
  }
134
+ /**
135
+ * @inheritdoc
136
+ * @see {@link K8sInit.PatchStatus}
137
+ */
138
+ async function PatchStatus(resource) {
139
+ syncFilters(resource);
140
+ return (0, utils_1.k8sExec)(model, filters, "PATCH_STATUS", resource);
141
+ }
134
142
  /**
135
143
  * @inheritdoc
136
144
  * @see {@link K8sInit.Watch}
@@ -142,8 +150,8 @@ function K8s(model, filters = {}) {
142
150
  * @inheritdoc
143
151
  * @see {@link K8sInit.Raw}
144
152
  */
145
- async function Raw(url) {
146
- const thing = await (0, utils_1.k8sCfg)("GET");
153
+ async function Raw(url, method = "GET") {
154
+ const thing = await (0, utils_1.k8sCfg)(method);
147
155
  const { opts, serverUrl } = thing;
148
156
  const resp = await (0, fetch_1.fetch)(`${serverUrl}${url}`, opts);
149
157
  if (resp.ok) {
@@ -151,6 +159,6 @@ function K8s(model, filters = {}) {
151
159
  }
152
160
  throw resp;
153
161
  }
154
- return { InNamespace, Apply, Create, Patch, Raw, ...withFilters };
162
+ return { InNamespace, Apply, Create, Patch, PatchStatus, Raw, ...withFilters };
155
163
  }
156
164
  exports.K8s = K8s;
@@ -81,6 +81,35 @@ const generateFakePodManagedFields = (manager) => {
81
81
  (0, globals_1.expect)(result).toEqual(fakeResource);
82
82
  (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, {}, "PATCH", patchOperations);
83
83
  });
84
+ (0, globals_1.it)("should patch the status of a resource", async () => {
85
+ await (0, _1.K8s)(upstream_1.Pod).PatchStatus({
86
+ metadata: {
87
+ name: "fake",
88
+ namespace: "default",
89
+ managedFields: generateFakePodManagedFields("pepr"),
90
+ },
91
+ spec: { priority: 3 },
92
+ status: {
93
+ phase: "Ready",
94
+ },
95
+ });
96
+ (0, globals_1.expect)(utils_1.k8sExec).toBeCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
97
+ name: "fake",
98
+ namespace: "default",
99
+ }), "PATCH_STATUS", {
100
+ apiVersion: "v1",
101
+ kind: "Pod",
102
+ metadata: {
103
+ name: "fake",
104
+ namespace: "default",
105
+ managedFields: generateFakePodManagedFields("pepr"),
106
+ },
107
+ spec: { priority: 3 },
108
+ status: {
109
+ phase: "Ready",
110
+ },
111
+ });
112
+ });
84
113
  (0, globals_1.it)("should filter with WithField", async () => {
85
114
  await (0, _1.K8s)(upstream_1.Pod).WithField("metadata.name", "fake").Get();
86
115
  (0, globals_1.expect)(mockedKubeExec).toHaveBeenCalledWith(upstream_1.Pod, globals_1.expect.objectContaining({
@@ -2,7 +2,6 @@ import { KubernetesListObject, KubernetesObject } from "@kubernetes/client-node"
2
2
  import { Operation } from "fast-json-patch";
3
3
  import type { PartialDeep } from "type-fest";
4
4
  import { GenericClass, GroupVersionKind } from "../types";
5
- import { ApplyCfg } from "./apply";
6
5
  import { WatchCfg, Watcher } from "./watch";
7
6
  /**
8
7
  * The Phase matched when using the K8s Watch API.
@@ -14,7 +13,7 @@ export declare enum WatchPhase {
14
13
  Bookmark = "BOOKMARK",
15
14
  Error = "ERROR"
16
15
  }
17
- export type FetchMethods = "GET" | "APPLY" | "POST" | "PUT" | "DELETE" | "PATCH" | "WATCH";
16
+ export type FetchMethods = "GET" | "APPLY" | "POST" | "PUT" | "DELETE" | "PATCH" | "WATCH" | "PATCH_STATUS";
18
17
  export interface Filters {
19
18
  kindOverride?: GroupVersionKind;
20
19
  fields?: Record<string, string>;
@@ -81,6 +80,19 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
81
80
  * @returns The patched resource
82
81
  */
83
82
  Patch: (payload: Operation[]) => Promise<K>;
83
+ /**
84
+ * Patch the status of the provided K8s resource. Note this is a special case of the Patch method that
85
+ * only allows patching the status subresource. This can be used in Operator reconciliation loops to
86
+ * update the status of a resource without triggering a new Generation of the resource.
87
+ *
88
+ * See https://stackoverflow.com/q/47100389/467373 for more details.
89
+ *
90
+ * IMPORTANT: This method will throw a 404 error if the resource does not have a status subresource defined.
91
+ *
92
+ * @param resource - the resource to patch
93
+ * @returns the patched resource
94
+ */
95
+ PatchStatus: (resource: PartialDeep<K>) => Promise<K>;
84
96
  /**
85
97
  * Perform a raw GET request to the Kubernetes API. This is useful for calling endpoints that are not supported by the fluent API.
86
98
  * This command mirrors the `kubectl get --raw` command.
@@ -98,7 +110,7 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
98
110
  * @param url the URL to call (e.g. /api)
99
111
  * @returns
100
112
  */
101
- Raw: (url: string) => Promise<K>;
113
+ Raw: (url: string, method?: FetchMethods) => Promise<K>;
102
114
  };
103
115
  export type K8sWithFilters<T extends GenericClass, K extends KubernetesObject> = K8sFilteredActions<T, K> & {
104
116
  /**
@@ -141,6 +153,15 @@ export type K8sWithFilters<T extends GenericClass, K extends KubernetesObject> =
141
153
  */
142
154
  WithLabel: (key: string, value?: string) => K8sWithFilters<T, K>;
143
155
  };
156
+ /**
157
+ * Configuration for the apply function.
158
+ */
159
+ export type ApplyCfg = {
160
+ /**
161
+ * Force the apply to be a create.
162
+ */
163
+ force?: boolean;
164
+ };
144
165
  export type K8sInit<T extends GenericClass, K extends KubernetesObject> = K8sWithFilters<T, K> & K8sUnfilteredActions<K> & {
145
166
  /**
146
167
  * Set the namespace filter.
@@ -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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"}
@@ -1,8 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import { URL } from "url";
3
3
  import { GenericClass } from "../types";
4
- import { FetchMethods, Filters } from "./types";
5
- import { ApplyCfg } from "./apply";
4
+ import { ApplyCfg, FetchMethods, Filters } from "./types";
6
5
  /**
7
6
  * Generate a path to a Kubernetes resource
8
7
  *
@@ -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;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,OAwDpB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY;;;GAwBhD;AAED;;;;;;;;;;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,cA8BtC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/fluent/utils.ts"],"names":[],"mappings":";AAKA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI1B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAI1D;;;;;;;;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;;;GAwBhD;AAED;;;;;;;;;;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,cA2CtC"}
@@ -108,6 +108,13 @@ async function k8sExec(model, filters, method, payload, applyCfg = { force: fals
108
108
  const { opts, serverUrl } = await k8sCfg(method);
109
109
  const url = pathBuilder(serverUrl, model, filters, method === "POST");
110
110
  switch (opts.method) {
111
+ // PATCH_STATUS is a special case that uses the PATCH method on status subresources
112
+ case "PATCH_STATUS":
113
+ opts.method = "PATCH";
114
+ url.pathname = `${url.pathname}/status`;
115
+ opts.headers.set("Content-Type", client_node_1.PatchStrategy.MergePatch);
116
+ payload = { status: payload.status };
117
+ break;
111
118
  case "PATCH":
112
119
  opts.headers.set("Content-Type", client_node_1.PatchStrategy.JsonPatch);
113
120
  break;
@@ -126,6 +133,10 @@ async function k8sExec(model, filters, method, payload, applyCfg = { force: fals
126
133
  if (resp.ok) {
127
134
  return resp.data;
128
135
  }
136
+ if (resp.status === 404 && method === "PATCH_STATUS") {
137
+ resp.statusText =
138
+ "Not Found" + " (NOTE: This error is expected if the resource has no status subresource)";
139
+ }
129
140
  throw resp;
130
141
  }
131
142
  exports.k8sExec = k8sExec;
@@ -3,10 +3,11 @@
3
3
  // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  const globals_1 = require("@jest/globals");
6
+ const node_fetch_1 = require("node-fetch");
6
7
  const fetch_1 = require("../fetch");
8
+ const kinds_1 = require("../kinds");
7
9
  const upstream_1 = require("../upstream");
8
10
  const utils_1 = require("./utils");
9
- const kinds_1 = require("../kinds");
10
11
  globals_1.jest.mock("https");
11
12
  globals_1.jest.mock("../fetch");
12
13
  (0, globals_1.describe)("pathBuilder Function", () => {
@@ -92,14 +93,18 @@ globals_1.jest.mock("../fetch");
92
93
  const mockedFetch = globals_1.jest.mocked(fetch_1.fetch);
93
94
  const fakeFilters = { name: "fake", namespace: "default" };
94
95
  const fakeMethod = "GET";
95
- const fakePayload = { metadata: { name: "fake", namespace: "default" } };
96
+ const fakePayload = {
97
+ metadata: { name: "fake", namespace: "default" },
98
+ status: { phase: "Ready" },
99
+ };
96
100
  const fakeUrl = new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake");
97
101
  const fakeOpts = {
98
102
  body: JSON.stringify(fakePayload),
99
- headers: {
103
+ compress: true,
104
+ headers: new node_fetch_1.Headers({
100
105
  "Content-Type": "application/json",
101
106
  "User-Agent": `kubernetes-fluent-client`,
102
- },
107
+ }),
103
108
  method: fakeMethod,
104
109
  };
105
110
  (0, globals_1.beforeEach)(() => {
@@ -116,6 +121,83 @@ globals_1.jest.mock("../fetch");
116
121
  (0, globals_1.expect)(result).toEqual(fakePayload);
117
122
  (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(fakeUrl, globals_1.expect.objectContaining(fakeOpts));
118
123
  });
124
+ (0, globals_1.it)("should handle PATCH_STATUS", async () => {
125
+ mockedFetch.mockResolvedValueOnce({
126
+ ok: true,
127
+ data: fakePayload,
128
+ status: 200,
129
+ statusText: "OK",
130
+ });
131
+ const result = await (0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, "PATCH_STATUS", fakePayload);
132
+ (0, globals_1.expect)(result).toEqual(fakePayload);
133
+ (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake/status"), globals_1.expect.objectContaining({
134
+ method: "PATCH",
135
+ compress: true,
136
+ headers: new node_fetch_1.Headers({
137
+ "Content-Type": "application/merge-patch+json",
138
+ "User-Agent": `kubernetes-fluent-client`,
139
+ }),
140
+ body: JSON.stringify({ status: fakePayload.status }),
141
+ }));
142
+ });
143
+ (0, globals_1.it)("should handle PATCH", async () => {
144
+ mockedFetch.mockResolvedValueOnce({
145
+ ok: true,
146
+ data: fakePayload,
147
+ status: 200,
148
+ statusText: "OK",
149
+ });
150
+ const patchPayload = [{ op: "replace", path: "/status/phase", value: "Ready" }];
151
+ const result = await (0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, "PATCH", patchPayload);
152
+ (0, globals_1.expect)(result).toEqual(fakePayload);
153
+ (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake"), globals_1.expect.objectContaining({
154
+ method: "PATCH",
155
+ compress: true,
156
+ headers: new node_fetch_1.Headers({
157
+ "Content-Type": "application/json-patch+json",
158
+ "User-Agent": `kubernetes-fluent-client`,
159
+ }),
160
+ body: JSON.stringify(patchPayload),
161
+ }));
162
+ });
163
+ (0, globals_1.it)("should handle APPLY", async () => {
164
+ mockedFetch.mockResolvedValueOnce({
165
+ ok: true,
166
+ data: fakePayload,
167
+ status: 200,
168
+ statusText: "OK",
169
+ });
170
+ const result = await (0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, "APPLY", fakePayload);
171
+ (0, globals_1.expect)(result).toEqual(fakePayload);
172
+ (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake?fieldManager=pepr&fieldValidation=Strict&force=false"), globals_1.expect.objectContaining({
173
+ method: "PATCH",
174
+ compress: true,
175
+ headers: new node_fetch_1.Headers({
176
+ "Content-Type": "application/apply-patch+yaml",
177
+ "User-Agent": `kubernetes-fluent-client`,
178
+ }),
179
+ body: JSON.stringify(fakePayload),
180
+ }));
181
+ });
182
+ (0, globals_1.it)("should handle APPLY with force", async () => {
183
+ mockedFetch.mockResolvedValueOnce({
184
+ ok: true,
185
+ data: fakePayload,
186
+ status: 200,
187
+ statusText: "OK",
188
+ });
189
+ const result = await (0, utils_1.k8sExec)(upstream_1.Pod, fakeFilters, "APPLY", fakePayload, { force: true });
190
+ (0, globals_1.expect)(result).toEqual(fakePayload);
191
+ (0, globals_1.expect)(mockedFetch).toHaveBeenCalledWith(new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake?fieldManager=pepr&fieldValidation=Strict&force=true"), globals_1.expect.objectContaining({
192
+ method: "PATCH",
193
+ compress: true,
194
+ headers: new node_fetch_1.Headers({
195
+ "Content-Type": "application/apply-patch+yaml",
196
+ "User-Agent": `kubernetes-fluent-client`,
197
+ }),
198
+ body: JSON.stringify(fakePayload),
199
+ }));
200
+ });
119
201
  (0, globals_1.it)("should handle fetch call failure", async () => {
120
202
  const fakeStatus = 404;
121
203
  const fakeStatusText = "Not Found";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "2.0.1",
4
- "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply",
3
+ "version": "2.1.3",
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",
7
7
  "types": "dist/index.d.ts",
@@ -40,25 +40,25 @@
40
40
  "fast-json-patch": "3.1.1",
41
41
  "http-status-codes": "2.3.0",
42
42
  "node-fetch": "2.7.0",
43
- "quicktype-core": "23.0.80",
44
- "type-fest": "4.9.0",
43
+ "quicktype-core": "23.0.104",
44
+ "type-fest": "4.10.2",
45
45
  "yargs": "17.7.2"
46
46
  },
47
47
  "devDependencies": {
48
- "@commitlint/cli": "18.4.3",
49
- "@commitlint/config-conventional": "18.4.3",
48
+ "@commitlint/cli": "18.6.1",
49
+ "@commitlint/config-conventional": "18.6.2",
50
50
  "@jest/globals": "29.7.0",
51
51
  "@types/byline": "4.2.36",
52
52
  "@types/readable-stream": "4.0.10",
53
53
  "@types/yargs": "17.0.32",
54
- "@typescript-eslint/eslint-plugin": "6.16.0",
55
- "@typescript-eslint/parser": "6.16.0",
56
- "eslint-plugin-jsdoc": "46.9.1",
54
+ "@typescript-eslint/eslint-plugin": "7.0.1",
55
+ "@typescript-eslint/parser": "7.0.1",
56
+ "eslint-plugin-jsdoc": "48.1.0",
57
57
  "jest": "29.7.0",
58
- "nock": "13.4.0",
59
- "prettier": "3.1.1",
60
- "semantic-release": "22.0.12",
61
- "ts-jest": "29.1.1",
58
+ "nock": "13.5.1",
59
+ "prettier": "3.2.5",
60
+ "semantic-release": "23.0.2",
61
+ "ts-jest": "29.1.2",
62
62
  "typescript": "5.3.3"
63
63
  },
64
64
  "release": {
@@ -102,6 +102,42 @@ describe("Kube", () => {
102
102
  expect(mockedKubeExec).toHaveBeenCalledWith(Pod, {}, "PATCH", patchOperations);
103
103
  });
104
104
 
105
+ it("should patch the status of a resource", async () => {
106
+ await K8s(Pod).PatchStatus({
107
+ metadata: {
108
+ name: "fake",
109
+ namespace: "default",
110
+ managedFields: generateFakePodManagedFields("pepr"),
111
+ },
112
+ spec: { priority: 3 },
113
+ status: {
114
+ phase: "Ready",
115
+ },
116
+ });
117
+
118
+ expect(k8sExec).toBeCalledWith(
119
+ Pod,
120
+ expect.objectContaining({
121
+ name: "fake",
122
+ namespace: "default",
123
+ }),
124
+ "PATCH_STATUS",
125
+ {
126
+ apiVersion: "v1",
127
+ kind: "Pod",
128
+ metadata: {
129
+ name: "fake",
130
+ namespace: "default",
131
+ managedFields: generateFakePodManagedFields("pepr"),
132
+ },
133
+ spec: { priority: 3 },
134
+ status: {
135
+ phase: "Ready",
136
+ },
137
+ },
138
+ );
139
+ });
140
+
105
141
  it("should filter with WithField", async () => {
106
142
  await K8s(Pod).WithField("metadata.name", "fake").Get();
107
143
 
@@ -9,8 +9,7 @@ import type { PartialDeep } from "type-fest";
9
9
  import { fetch } from "../fetch";
10
10
  import { modelToGroupVersionKind } from "../kinds";
11
11
  import { GenericClass } from "../types";
12
- import { ApplyCfg } from "./apply";
13
- import { Filters, K8sInit, Paths, WatchAction } from "./types";
12
+ import { ApplyCfg, FetchMethods, Filters, K8sInit, Paths, WatchAction } from "./types";
14
13
  import { k8sCfg, k8sExec } from "./utils";
15
14
  import { WatchCfg, Watcher } from "./watch";
16
15
 
@@ -162,6 +161,15 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
162
161
  return k8sExec(model, filters, "PATCH", payload);
163
162
  }
164
163
 
164
+ /**
165
+ * @inheritdoc
166
+ * @see {@link K8sInit.PatchStatus}
167
+ */
168
+ async function PatchStatus(resource: PartialDeep<K>): Promise<K> {
169
+ syncFilters(resource as K);
170
+ return k8sExec(model, filters, "PATCH_STATUS", resource);
171
+ }
172
+
165
173
  /**
166
174
  * @inheritdoc
167
175
  * @see {@link K8sInit.Watch}
@@ -174,8 +182,8 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
174
182
  * @inheritdoc
175
183
  * @see {@link K8sInit.Raw}
176
184
  */
177
- async function Raw(url: string) {
178
- const thing = await k8sCfg("GET");
185
+ async function Raw(url: string, method: FetchMethods = "GET") {
186
+ const thing = await k8sCfg(method);
179
187
  const { opts, serverUrl } = thing;
180
188
  const resp = await fetch<K>(`${serverUrl}${url}`, opts);
181
189
 
@@ -186,5 +194,5 @@ export function K8s<T extends GenericClass, K extends KubernetesObject = Instanc
186
194
  throw resp;
187
195
  }
188
196
 
189
- return { InNamespace, Apply, Create, Patch, Raw, ...withFilters };
197
+ return { InNamespace, Apply, Create, Patch, PatchStatus, Raw, ...withFilters };
190
198
  }
@@ -6,7 +6,6 @@ import { Operation } from "fast-json-patch";
6
6
  import type { PartialDeep } from "type-fest";
7
7
 
8
8
  import { GenericClass, GroupVersionKind } from "../types";
9
- import { ApplyCfg } from "./apply";
10
9
  import { WatchCfg, Watcher } from "./watch";
11
10
 
12
11
  /**
@@ -20,7 +19,15 @@ export enum WatchPhase {
20
19
  Error = "ERROR",
21
20
  }
22
21
 
23
- export type FetchMethods = "GET" | "APPLY" | "POST" | "PUT" | "DELETE" | "PATCH" | "WATCH";
22
+ export type FetchMethods =
23
+ | "GET"
24
+ | "APPLY"
25
+ | "POST"
26
+ | "PUT"
27
+ | "DELETE"
28
+ | "PATCH"
29
+ | "WATCH"
30
+ | "PATCH_STATUS";
24
31
 
25
32
  export interface Filters {
26
33
  kindOverride?: GroupVersionKind;
@@ -96,6 +103,20 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
96
103
  */
97
104
  Patch: (payload: Operation[]) => Promise<K>;
98
105
 
106
+ /**
107
+ * Patch the status of the provided K8s resource. Note this is a special case of the Patch method that
108
+ * only allows patching the status subresource. This can be used in Operator reconciliation loops to
109
+ * update the status of a resource without triggering a new Generation of the resource.
110
+ *
111
+ * See https://stackoverflow.com/q/47100389/467373 for more details.
112
+ *
113
+ * IMPORTANT: This method will throw a 404 error if the resource does not have a status subresource defined.
114
+ *
115
+ * @param resource - the resource to patch
116
+ * @returns the patched resource
117
+ */
118
+ PatchStatus: (resource: PartialDeep<K>) => Promise<K>;
119
+
99
120
  /**
100
121
  * Perform a raw GET request to the Kubernetes API. This is useful for calling endpoints that are not supported by the fluent API.
101
122
  * This command mirrors the `kubectl get --raw` command.
@@ -113,7 +134,7 @@ export type K8sUnfilteredActions<K extends KubernetesObject> = {
113
134
  * @param url the URL to call (e.g. /api)
114
135
  * @returns
115
136
  */
116
- Raw: (url: string) => Promise<K>;
137
+ Raw: (url: string, method?: FetchMethods) => Promise<K>;
117
138
  };
118
139
 
119
140
  export type K8sWithFilters<T extends GenericClass, K extends KubernetesObject> = K8sFilteredActions<
@@ -162,6 +183,16 @@ export type K8sWithFilters<T extends GenericClass, K extends KubernetesObject> =
162
183
  WithLabel: (key: string, value?: string) => K8sWithFilters<T, K>;
163
184
  };
164
185
 
186
+ /**
187
+ * Configuration for the apply function.
188
+ */
189
+ export type ApplyCfg = {
190
+ /**
191
+ * Force the apply to be a create.
192
+ */
193
+ force?: boolean;
194
+ };
195
+
165
196
  export type K8sInit<T extends GenericClass, K extends KubernetesObject> = K8sWithFilters<T, K> &
166
197
  K8sUnfilteredActions<K> & {
167
198
  /**
@@ -2,13 +2,14 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
+ import { Headers } from "node-fetch";
5
6
 
6
7
  import { fetch } from "../fetch";
8
+ import { RegisterKind } from "../kinds";
7
9
  import { GenericClass } from "../types";
8
10
  import { ClusterRole, Ingress, Pod } from "../upstream";
9
11
  import { Filters } from "./types";
10
12
  import { k8sExec, pathBuilder } from "./utils";
11
- import { RegisterKind } from "../kinds";
12
13
 
13
14
  jest.mock("https");
14
15
  jest.mock("../fetch");
@@ -115,14 +116,18 @@ describe("kubeExec Function", () => {
115
116
 
116
117
  const fakeFilters: Filters = { name: "fake", namespace: "default" };
117
118
  const fakeMethod = "GET";
118
- const fakePayload = { metadata: { name: "fake", namespace: "default" } };
119
+ const fakePayload = {
120
+ metadata: { name: "fake", namespace: "default" },
121
+ status: { phase: "Ready" },
122
+ };
119
123
  const fakeUrl = new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake");
120
124
  const fakeOpts = {
121
125
  body: JSON.stringify(fakePayload),
122
- headers: {
126
+ compress: true,
127
+ headers: new Headers({
123
128
  "Content-Type": "application/json",
124
129
  "User-Agent": `kubernetes-fluent-client`,
125
- },
130
+ }),
126
131
  method: fakeMethod,
127
132
  };
128
133
 
@@ -144,6 +149,112 @@ describe("kubeExec Function", () => {
144
149
  expect(mockedFetch).toHaveBeenCalledWith(fakeUrl, expect.objectContaining(fakeOpts));
145
150
  });
146
151
 
152
+ it("should handle PATCH_STATUS", async () => {
153
+ mockedFetch.mockResolvedValueOnce({
154
+ ok: true,
155
+ data: fakePayload,
156
+ status: 200,
157
+ statusText: "OK",
158
+ });
159
+
160
+ const result = await k8sExec(Pod, fakeFilters, "PATCH_STATUS", fakePayload);
161
+
162
+ expect(result).toEqual(fakePayload);
163
+ expect(mockedFetch).toHaveBeenCalledWith(
164
+ new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake/status"),
165
+ expect.objectContaining({
166
+ method: "PATCH",
167
+ compress: true,
168
+ headers: new Headers({
169
+ "Content-Type": "application/merge-patch+json",
170
+ "User-Agent": `kubernetes-fluent-client`,
171
+ }),
172
+ body: JSON.stringify({ status: fakePayload.status }),
173
+ }),
174
+ );
175
+ });
176
+
177
+ it("should handle PATCH", async () => {
178
+ mockedFetch.mockResolvedValueOnce({
179
+ ok: true,
180
+ data: fakePayload,
181
+ status: 200,
182
+ statusText: "OK",
183
+ });
184
+
185
+ const patchPayload = [{ op: "replace", path: "/status/phase", value: "Ready" }];
186
+
187
+ const result = await k8sExec(Pod, fakeFilters, "PATCH", patchPayload);
188
+
189
+ expect(result).toEqual(fakePayload);
190
+ expect(mockedFetch).toHaveBeenCalledWith(
191
+ new URL("http://jest-test:8080/api/v1/namespaces/default/pods/fake"),
192
+ expect.objectContaining({
193
+ method: "PATCH",
194
+ compress: true,
195
+ headers: new Headers({
196
+ "Content-Type": "application/json-patch+json",
197
+ "User-Agent": `kubernetes-fluent-client`,
198
+ }),
199
+ body: JSON.stringify(patchPayload),
200
+ }),
201
+ );
202
+ });
203
+
204
+ it("should handle APPLY", async () => {
205
+ mockedFetch.mockResolvedValueOnce({
206
+ ok: true,
207
+ data: fakePayload,
208
+ status: 200,
209
+ statusText: "OK",
210
+ });
211
+
212
+ const result = await k8sExec(Pod, fakeFilters, "APPLY", fakePayload);
213
+
214
+ expect(result).toEqual(fakePayload);
215
+ expect(mockedFetch).toHaveBeenCalledWith(
216
+ new URL(
217
+ "http://jest-test:8080/api/v1/namespaces/default/pods/fake?fieldManager=pepr&fieldValidation=Strict&force=false",
218
+ ),
219
+ expect.objectContaining({
220
+ method: "PATCH",
221
+ compress: true,
222
+ headers: new Headers({
223
+ "Content-Type": "application/apply-patch+yaml",
224
+ "User-Agent": `kubernetes-fluent-client`,
225
+ }),
226
+ body: JSON.stringify(fakePayload),
227
+ }),
228
+ );
229
+ });
230
+
231
+ it("should handle APPLY with force", async () => {
232
+ mockedFetch.mockResolvedValueOnce({
233
+ ok: true,
234
+ data: fakePayload,
235
+ status: 200,
236
+ statusText: "OK",
237
+ });
238
+
239
+ const result = await k8sExec(Pod, fakeFilters, "APPLY", fakePayload, { force: true });
240
+
241
+ expect(result).toEqual(fakePayload);
242
+ expect(mockedFetch).toHaveBeenCalledWith(
243
+ new URL(
244
+ "http://jest-test:8080/api/v1/namespaces/default/pods/fake?fieldManager=pepr&fieldValidation=Strict&force=true",
245
+ ),
246
+ expect.objectContaining({
247
+ method: "PATCH",
248
+ compress: true,
249
+ headers: new Headers({
250
+ "Content-Type": "application/apply-patch+yaml",
251
+ "User-Agent": `kubernetes-fluent-client`,
252
+ }),
253
+ body: JSON.stringify(fakePayload),
254
+ }),
255
+ );
256
+ });
257
+
147
258
  it("should handle fetch call failure", async () => {
148
259
  const fakeStatus = 404;
149
260
  const fakeStatusText = "Not Found";
@@ -2,14 +2,13 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
 
4
4
  import { KubeConfig, PatchStrategy } from "@kubernetes/client-node";
5
-
6
5
  import { Headers } from "node-fetch";
7
6
  import { URL } from "url";
7
+
8
8
  import { fetch } from "../fetch";
9
9
  import { modelToGroupVersionKind } from "../kinds";
10
10
  import { GenericClass } from "../types";
11
- import { FetchMethods, Filters } from "./types";
12
- import { ApplyCfg } from "./apply";
11
+ import { ApplyCfg, FetchMethods, Filters } from "./types";
13
12
 
14
13
  const SSA_CONTENT_TYPE = "application/apply-patch+yaml";
15
14
 
@@ -143,6 +142,14 @@ export async function k8sExec<T extends GenericClass, K>(
143
142
  const url = pathBuilder(serverUrl, model, filters, method === "POST");
144
143
 
145
144
  switch (opts.method) {
145
+ // PATCH_STATUS is a special case that uses the PATCH method on status subresources
146
+ case "PATCH_STATUS":
147
+ opts.method = "PATCH";
148
+ url.pathname = `${url.pathname}/status`;
149
+ (opts.headers as Headers).set("Content-Type", PatchStrategy.MergePatch);
150
+ payload = { status: (payload as { status: unknown }).status };
151
+ break;
152
+
146
153
  case "PATCH":
147
154
  (opts.headers as Headers).set("Content-Type", PatchStrategy.JsonPatch);
148
155
  break;
@@ -166,5 +173,10 @@ export async function k8sExec<T extends GenericClass, K>(
166
173
  return resp.data;
167
174
  }
168
175
 
176
+ if (resp.status === 404 && method === "PATCH_STATUS") {
177
+ resp.statusText =
178
+ "Not Found" + " (NOTE: This error is expected if the resource has no status subresource)";
179
+ }
180
+
169
181
  throw resp;
170
182
  }
@@ -1,10 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,4 +0,0 @@
1
- "use strict";
2
- // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +0,0 @@
1
- // SPDX-License-Identifier: Apache-2.0
2
- // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client 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
- };