Skip to content

Commit f1463a6

Browse files
refactor(juno): fix typescript errors (k8s client) (#843)
* fix: Fix k8s-client watch error * fix(juno): replace regular function class methods with arrow functions * refactor(juno): fix typescript errors (k8s client) * refactor(juno): create changeset * refactor(juno): enhance k8s api error type definition fixes #812
1 parent 0eb6d9e commit f1463a6

File tree

8 files changed

+117
-74
lines changed

8 files changed

+117
-74
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudoperators/juno-k8s-client": patch
3+
---
4+
5+
fix typescript errors (k8s client)

packages/k8s-client/eslint.config.mjs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,7 @@ export default [
1414
project: ["./tsconfig.json"], // Ensure this points to your tsconfig.json
1515
},
1616
},
17-
// TODO: We need to make all of this checks on again, step by step
18-
rules: {
19-
"@typescript-eslint/no-unsafe-assignment": "off",
20-
"@typescript-eslint/no-unsafe-call": "off",
21-
"@typescript-eslint/no-unsafe-argument": "off",
22-
"@typescript-eslint/no-floating-promises": "off",
23-
"@typescript-eslint/no-unsafe-member-access": "off",
24-
},
17+
rules: {},
2518
ignores: ["vitest.config.ts", "vite.config.ts"],
2619
},
2720
]

packages/k8s-client/src/apiErrorHandler.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,21 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
interface ApiErrorType extends Error {
6+
export interface K8sApiError extends Error {
7+
apiVersion?: string
8+
kind?: string
9+
message: string
10+
metadata?: Record<string, any>
11+
reason?: string
12+
status?: string
13+
code?: number
714
response?: {
815
data?: { message?: string }
16+
status?: number
917
}
1018
}
11-
const apiErrorHandler = async (apiError: ApiErrorType): Promise<never> => {
19+
20+
const apiErrorHandler = async (apiError: K8sApiError): Promise<K8sApiError> => {
1221
const error = apiError.response?.data ? new Error(apiError.response.data.message ?? apiError.message) : apiError
1322

1423
return Promise.reject(error)

packages/k8s-client/src/client.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import request from "./request"
77
import { buildUrl } from "./urlHelpers"
88
import { Watch, ADDED, MODIFIED, DELETED, ERROR } from "./watch"
9-
import handleApiError from "./apiErrorHandler"
9+
import handleApiError, { K8sApiError } from "./apiErrorHandler"
1010

1111
interface ClientOptions {
1212
apiEndpoint: string
@@ -17,7 +17,7 @@ interface ClientOptions {
1717
interface RequestOptions {
1818
params?: Record<string, any>
1919
headers?: Record<string, string>
20-
body?: any
20+
body?: Object | null
2121
signal?: AbortSignal
2222
mode?: RequestMode
2323
cache?: RequestCache
@@ -51,30 +51,30 @@ function createClient(options: ClientOptions) {
5151
}
5252
}
5353

54-
function head(path: string, options: RequestOptions = {}): Promise<Response> {
54+
function head(path: string, options: RequestOptions = {}): Promise<Response | K8sApiError> {
5555
return request("HEAD", buildUrl(apiEndpoint, path), extendOptions(options)).catch(handleApiError)
5656
}
5757

58-
function get(path: string, options: RequestOptions = {}): Promise<any> {
58+
function get(path: string, options: RequestOptions = {}): Promise<unknown> {
5959
return request("GET", buildUrl(apiEndpoint, path), extendOptions(options))
6060
.then((res) => res.json())
6161
.catch(handleApiError)
6262
}
6363

64-
function post(path: string, data: {}, options: RequestOptions = {}): Promise<any> {
64+
function post(path: string, data: {}, options: RequestOptions = {}): Promise<unknown> {
6565
const result = request("POST", buildUrl(apiEndpoint, path), extendOptions(options, { body: data }))
6666
.then((res) => res.json())
6767
.catch(handleApiError)
6868
return result
6969
}
7070

71-
function put(path: string, data: {}, options: RequestOptions = {}): Promise<any> {
71+
function put(path: string, data: {}, options: RequestOptions = {}): Promise<unknown> {
7272
return request("PUT", buildUrl(apiEndpoint, path), extendOptions(options, { body: data }))
7373
.then((res) => res.json())
7474
.catch(handleApiError)
7575
}
7676

77-
function patch(path: string, data: {}, options: RequestOptions = {}): Promise<any> {
77+
function patch(path: string, data: {}, options: RequestOptions = {}): Promise<unknown> {
7878
return request(
7979
"PATCH",
8080
buildUrl(apiEndpoint, path),
@@ -87,7 +87,7 @@ function createClient(options: ClientOptions) {
8787
.catch(handleApiError)
8888
}
8989

90-
function del(path: string, data?: {} | null, options: RequestOptions = {}): Promise<any> {
90+
function del(path: string, data?: {} | null, options: RequestOptions = {}): Promise<unknown> {
9191
return request("DELETE", buildUrl(apiEndpoint, path), extendOptions(options, { body: data }))
9292
.then((res) => res.json())
9393
.catch(handleApiError)

packages/k8s-client/src/request.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55

66
import { buildUrl } from "./urlHelpers"
77
import * as logger from "./logger"
8+
import { K8sApiError } from "./apiErrorHandler"
89

910
// Define the shape of the options parameter
1011
interface RequestOptions {
1112
params?: Record<string, any>
1213
signal?: AbortSignal
13-
headers?: HeadersInit
14-
body?: any
14+
headers?: HeadersInit | null
15+
body?: Object | null
1516
mode?: RequestMode
1617
cache?: RequestCache
1718
credentials?: RequestCredentials
19+
[key: string]: any
1820
}
1921

2022
// Check response status
@@ -23,7 +25,7 @@ const checkStatus = (response: Response): Response => {
2325
return response
2426
} else {
2527
const error = new Error(response.statusText || `${response.status}`)
26-
;(error as any).response = response // Type assertion to attach the response to the error
28+
;(error as K8sApiError).response = response // Type assertion to attach the response to the error
2729
throw error
2830
}
2931
}
@@ -41,14 +43,17 @@ function request(method: string, url: string, options: RequestOptions = {}): Pro
4143
if (options.params) url = buildUrl(url, options.params)
4244

4345
// add allowed options to fetch
44-
const fetchOptions: RequestInit = ["signal", "headers", "body", "mode", "cache", "credentials"].reduce(
46+
const requestFields = ["signal", "headers", "body", "mode", "cache", "credentials"] as const
47+
48+
const fetchOptions: RequestInit = requestFields.reduce(
4549
(map, key) => {
46-
if (options[key as keyof RequestOptions]) {
47-
map[key as keyof RequestInit] = options[key as keyof RequestOptions]
50+
if (options[key]) {
51+
return { ...map, [key]: options[key] }
4852
}
53+
4954
return map
5055
},
51-
{ credentials: "same-origin", method } as RequestInit
56+
{ credentials: "same-origin", method }
5257
)
5358

5459
// stringify body if it's an object

packages/k8s-client/src/watch.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,38 @@
55

66
import request from "./request"
77
import * as logger from "./logger"
8+
import { K8sApiError } from "./apiErrorHandler"
89

910
const ADDED = "ADDED"
1011
const MODIFIED = "MODIFIED"
1112
const DELETED = "DELETED"
1213
const ERROR = "ERROR"
1314
const BOOKMARK = "BOOKMARK"
1415

15-
type Listener = (_items: any) => void
16+
type Listener = (_items: unknown) => void
17+
18+
interface WatchEvent {
19+
type: string
20+
object?: {
21+
metadata?: {
22+
resourceVersion?: string
23+
}
24+
code?: number
25+
}
26+
}
27+
28+
interface ApiResponseData {
29+
items: unknown
30+
metadata: {
31+
resourceVersion: string | null
32+
}
33+
}
1634

1735
interface WatchOptions {
18-
params?: Record<string, any>
36+
params?: Record<string, string>
1937
headers?: Record<string, string>
2038
signal?: AbortSignal
21-
body?: any
39+
body?: Object | null
2240
mode?: RequestMode
2341
cache?: RequestCache
2442
credentials?: RequestCredentials
@@ -69,7 +87,7 @@ class Watch {
6987
this.listeners[type].push(listener)
7088
}
7189

72-
private informListeners = (type: string, items: any) => {
90+
private informListeners = (type: string, items: unknown) => {
7391
const listeners = this.listeners[type]
7492
if (listeners) {
7593
listeners.forEach((listener) => {
@@ -83,7 +101,7 @@ class Watch {
83101
}
84102
}
85103

86-
private handleEvents = (events: any) => {
104+
private handleEvents = (events: WatchEvent | WatchEvent[]) => {
87105
if (!Array.isArray(events)) events = [events]
88106

89107
// Pass correct "this" context to be referenced in asynchronous callbacks
@@ -92,9 +110,9 @@ class Watch {
92110
setTimeout(() => {
93111
const eventsByType: Record<string, any[]> = {}
94112

95-
events.forEach((event: any) => {
113+
events.forEach((event: WatchEvent) => {
96114
if (event.type === BOOKMARK) {
97-
that.resourceVersion = event.object.metadata.resourceVersion
115+
that.resourceVersion = event?.object?.metadata?.resourceVersion ?? null
98116
} else {
99117
if (!eventsByType[event.type]) {
100118
eventsByType[event.type] = []
@@ -111,7 +129,10 @@ class Watch {
111129
if (this.resourceVersion) return this.resourceVersion
112130

113131
logger.debug(this.PREFIX, "get resource version from API")
114-
const { metadata, items } = await request("GET", this.url, this.options).then((response) => response.json())
132+
const { metadata, items } = (await request("GET", this.url, this.options).then((response) =>
133+
// 'Body.json()' type definition expects 'any' as a return value
134+
response.json()
135+
)) as ApiResponseData
115136

116137
this.resourceVersion = metadata.resourceVersion
117138
if (items) this.informListeners(ADDED, items)
@@ -155,10 +176,11 @@ class Watch {
155176
const events = data.split(/\n|\r|\r\n/)
156177
data = events.pop() ?? ""
157178

158-
const parsedEvents: any[] = []
179+
const parsedEvents: WatchEvent[] = []
159180
events.forEach((e) => {
160-
const parsedEvent = JSON.parse(e)
161-
if (parsedEvent.type === ERROR && parsedEvent.object.code === 410) {
181+
// 'JSON.parse' type definition expects 'any' as a return value
182+
const parsedEvent = JSON.parse(e) as WatchEvent
183+
if (parsedEvent.type === ERROR && parsedEvent?.object?.code === 410) {
162184
that.cancel()
163185
setTimeout(() => {
164186
logger.debug(that.PREFIX, "resource is gone 410", "recreate watch request!")
@@ -173,7 +195,7 @@ class Watch {
173195
that.handleEvents(parsedEvents)
174196
}
175197
})
176-
.catch((e) => {
198+
.catch((e: K8sApiError) => {
177199
if (e.name === "AbortError") return
178200
const status = e.code || e?.response?.status
179201

@@ -184,7 +206,7 @@ class Watch {
184206
return
185207
}
186208

187-
if ([404].includes(status)) {
209+
if (status === 404) {
188210
that.handleEvents({ type: ERROR, object: e })
189211
that.cancel()
190212
return

packages/k8s-client/test/client.test.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ describe("k8sClient", () => {
2222
describe("createClient", () => {
2323
describe("all required options are provided", () => {
2424
const options = { apiEndpoint: "test", token: "test" }
25+
const result = expect(() => createClient(options))
2526

2627
test("should not throw an error", () => {
27-
expect(() => createClient(options)).not.toThrow()
28+
result.not.toThrow()
2829
})
2930

3031
test("should return a client object", () => {
@@ -59,16 +60,18 @@ describe("Client", () => {
5960
expect(client.get).toBeDefined()
6061
})
6162

62-
test("call request", () => {
63-
client.get("/api/v1", { params: { key1: "value1", key2: "value2" } })
63+
test("call request", async () => {
64+
await client.get("/api/v1", { params: { key1: "value1", key2: "value2" } })
6465
expect(request).toHaveBeenLastCalledWith("GET", "https://test.com/api/v1", {
66+
// linter complains of the asymmetric matcher "expect.anything()" - unsafe 'any' value assignment
67+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
6568
headers: expect.anything(),
6669
params: { key1: "value1", key2: "value2" },
6770
})
6871
})
6972

70-
test("allow to override default options", () => {
71-
client.get("/api/v1", {
73+
test("allow to override default options", async () => {
74+
await client.get("/api/v1", {
7275
headers: { Authorization: "test", "Content-Type": "text" },
7376
})
7477
expect(request).toHaveBeenLastCalledWith("GET", "https://test.com/api/v1", {
@@ -82,9 +85,11 @@ describe("Client", () => {
8285
expect(client.post).toBeDefined()
8386
})
8487

85-
test("call request", () => {
86-
client.post("/api/v1", { key1: "value1" }, { params: { key1: "value1", key2: "value2" } })
88+
test("call request", async () => {
89+
await client.post("/api/v1", { key1: "value1" }, { params: { key1: "value1", key2: "value2" } })
8790
expect(request).toHaveBeenLastCalledWith("POST", "https://test.com/api/v1", {
91+
// linter complains of the asymmetric matcher "expect.anything()" - unsafe 'any' value assignment
92+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
8893
headers: expect.anything(),
8994
params: { key1: "value1", key2: "value2" },
9095
body: { key1: "value1" },
@@ -97,8 +102,8 @@ describe("Client", () => {
97102
expect(client.put).toBeDefined()
98103
})
99104

100-
test("call request", () => {
101-
client.put("/api/v1", { key1: "value1" }, { params: { key1: "value1", key2: "value2" } })
105+
test("call request", async () => {
106+
await client.put("/api/v1", { key1: "value1" }, { params: { key1: "value1", key2: "value2" } })
102107
expect(request).toHaveBeenLastCalledWith("PUT", "https://test.com/api/v1", {
103108
headers: {
104109
Authorization: "Bearer test",
@@ -115,8 +120,8 @@ describe("Client", () => {
115120
expect(client.patch).toBeDefined()
116121
})
117122

118-
test("call request", () => {
119-
client.patch(
123+
test("call request", async () => {
124+
await client.patch(
120125
"/api/v1",
121126
{ key1: "value1" },
122127
{
@@ -139,11 +144,13 @@ describe("Client", () => {
139144
expect(client.delete).toBeDefined()
140145
})
141146

142-
test("call request", () => {
143-
client.delete("/api/v1", null, {
147+
test("call request", async () => {
148+
await client.delete("/api/v1", null, {
144149
params: { key1: "value1", key2: "value2" },
145150
})
146151
expect(request).toHaveBeenLastCalledWith("DELETE", "https://test.com/api/v1", {
152+
// linter complains of the asymmetric matcher "expect.anything()" - unsafe 'any' value assignment
153+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
147154
headers: expect.anything(),
148155
body: null,
149156
params: { key1: "value1", key2: "value2" },
@@ -156,9 +163,11 @@ describe("Client", () => {
156163
expect(client.head).toBeDefined()
157164
})
158165

159-
test("call request", () => {
160-
client.head("/api/v1", { params: { key1: "value1", key2: "value2" } })
166+
test("call request", async () => {
167+
await client.head("/api/v1", { params: { key1: "value1", key2: "value2" } })
161168
expect(request).toHaveBeenLastCalledWith("HEAD", "https://test.com/api/v1", {
169+
// linter complains of the asymmetric matcher "expect.anything()" - unsafe 'any' value assignment
170+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
162171
headers: expect.anything(),
163172
params: { key1: "value1", key2: "value2" },
164173
})

0 commit comments

Comments
 (0)