Skip to content

Commit

Permalink
remove cache
Browse files Browse the repository at this point in the history
  • Loading branch information
kahirokunn committed Mar 12, 2024
1 parent 8dc67d1 commit e2a2578
Showing 1 changed file with 103 additions and 115 deletions.
218 changes: 103 additions & 115 deletions examples/generate-all-k8s-client/k8s-client/client.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
import * as k8s from "@kubernetes/client-node";
import NodeCache from "node-cache";
import fetch, { FetchError } from "node-fetch";
import fs from "node:fs";
import * as https from "node:https";

const fileCache = new NodeCache({ stdTTL: 3600, checkperiod: 120 });

async function readFile(filePath: string) {
if (!fileCache.has(filePath)) {
fileCache.set(filePath, await fs.promises.readFile(filePath, "utf8"));
}

return fileCache.get<string>(filePath);
}
import * as k8s from '@kubernetes/client-node'
import fetch, { FetchError } from 'node-fetch'
import * as https from 'node:https'

type RemoveUndefined<T> = {
[K in keyof T]: Exclude<T[K], undefined | null>;
};
[K in keyof T]: Exclude<T[K], undefined | null>
}

export function removeNullableProperties<
T extends Record<string, unknown | undefined> | undefined,
T extends Record<string, unknown | undefined> | undefined
>(object: T): RemoveUndefined<T> {
if (!object) return object as RemoveUndefined<T>;
if (!object) return object as RemoveUndefined<T>
for (const key of Object.keys(object))
(object[key] === undefined || object[key] === null) && delete object[key];
return object as RemoveUndefined<T>;
(object[key] === undefined || object[key] === null) && delete object[key]
return object as RemoveUndefined<T>
}

/**
Expand All @@ -41,119 +29,119 @@ export function removeNullableProperties<
* @param maxRetries - Maximum number of retries
*/
async function defaultBackoff(attempt: number, maxRetries: number) {
const attempts = Math.min(attempt, maxRetries);
const attempts = Math.min(attempt, maxRetries)

const timeout = Math.trunc((Math.random() + 0.4) * (300 << attempts));
const timeout = Math.trunc((Math.random() + 0.4) * (300 << attempts))
await new Promise((resolve) =>
setTimeout((response: any) => resolve(response), timeout),
);
setTimeout((response: any) => resolve(response), timeout)
)
}

const isPlainObject = (value: any) => value?.constructor === Object;
const isPlainObject = (value: any) => value?.constructor === Object

type QueryArgumentsSpec = {
path: string | undefined;
path: string | undefined
method?:
| "GET"
| "DELETE"
| "PATCH"
| "POST"
| "PUT"
| "OPTIONS"
| "HEAD"
| undefined;
body?: any | undefined;
contentType?: string | undefined;
params?: any | undefined;
};
| 'GET'
| 'DELETE'
| 'PATCH'
| 'POST'
| 'PUT'
| 'OPTIONS'
| 'HEAD'
| undefined
body?: any | undefined
contentType?: string | undefined
params?: any | undefined
}

type MaybePromise<T> = T | Promise<T>;
type MaybePromise<T> = T | Promise<T>

type InterceptorArguments = {
args: QueryArgumentsSpec;
opts: https.RequestOptions;
};
args: QueryArgumentsSpec
opts: https.RequestOptions
}
type Interceptor = (
arguments_: InterceptorArguments,
options: Options,
) => MaybePromise<https.RequestOptions>;
options: Options
) => MaybePromise<https.RequestOptions>

const interceptors: Interceptor[] = [
async function injectKubernetesParameters({ opts }) {
const kc = new k8s.KubeConfig();
kc.loadFromDefault();
const nextOptions: https.RequestOptions = { ...opts };
const kc = new k8s.KubeConfig()
kc.loadFromDefault()
const nextOptions: https.RequestOptions = { ...opts }
await kc.applyToHTTPSOptions(nextOptions)

const cluster = kc.getCurrentCluster()

if (cluster?.server) {
const url = new URL(cluster.server);
nextOptions.host = url.hostname;
nextOptions.protocol = url.protocol;
nextOptions.port = url.port;
const url = new URL(cluster.server)
nextOptions.host = url.hostname
nextOptions.protocol = url.protocol
nextOptions.port = url.port
}
return nextOptions;
return nextOptions
},
];
]

type RetryConditionFunction = (extraArguments: {
res?: Response;
error: unknown;
args: QueryArgumentsSpec;
attempt: number;
options: RetryOptions;
}) => boolean | Promise<boolean>;
res?: Response
error: unknown
args: QueryArgumentsSpec
attempt: number
options: RetryOptions
}) => boolean | Promise<boolean>

type RetryOptions = {
retryCondition?: RetryConditionFunction;
maxRetries?: number;
};
retryCondition?: RetryConditionFunction
maxRetries?: number
}

type HttpHeaderOptions = {
headers?: Record<string, string> | undefined;
};
headers?: Record<string, string> | undefined
}

export type Options = RetryOptions & HttpHeaderOptions;
export type Options = RetryOptions & HttpHeaderOptions

export async function apiClient<Response>(
arguments_: QueryArgumentsSpec,
extraOptions?: Options,
extraOptions?: Options
): Promise<Response> {
const maxRetries = extraOptions?.maxRetries ?? 3;
const maxRetries = extraOptions?.maxRetries ?? 3

const defaultRetryCondition: RetryConditionFunction = ({ ...object }) => {
const { res, attempt, error } = object;
const { res, attempt, error } = object
if (attempt > maxRetries) {
return false;
return false
}

if (error instanceof FetchError) {
return true;
return true
}
if (res && res.status >= 500) {
return true;
return true
}
return false;
};
return false
}

const options = {
maxRetries,
backoff: defaultBackoff,
retryCondition: defaultRetryCondition,
...removeNullableProperties(extraOptions),
};
}

let { path, method, params, body, contentType } = { ...arguments_ };
let { path, method, params, body, contentType } = { ...arguments_ }

let httpsOptions: https.RequestOptions = {
path,
headers: {
...options.headers,
},
};
}
if (method) {
httpsOptions.method = method;
httpsOptions.method = method
}

for (const interceptor of interceptors) {
Expand All @@ -162,8 +150,8 @@ export async function apiClient<Response>(
args: arguments_,
opts: httpsOptions,
},
options,
);
options
)
}

if (
Expand All @@ -176,42 +164,42 @@ export async function apiClient<Response>(
cert: httpsOptions.cert,
key: httpsOptions.key,
port: httpsOptions.port ? Number(httpsOptions.port) : undefined,
}),
);
httpsOptions.agent = agent;
})
)
httpsOptions.agent = agent
}

if (!httpsOptions.protocol) {
httpsOptions.protocol = "http:";
httpsOptions.protocol = 'http:'
}
const host = httpsOptions.host || httpsOptions.hostname;
let baseUrl = `${httpsOptions.protocol}//${host}`;
const searchParameters = toSearchParameters(params);
const host = httpsOptions.host || httpsOptions.hostname
let baseUrl = `${httpsOptions.protocol}//${host}`
const searchParameters = toSearchParameters(params)
if (searchParameters.size > 0) {
baseUrl += (baseUrl.includes("?") ? "&" : "?") + toSearchParameters(params);
baseUrl += (baseUrl.includes('?') ? '&' : '?') + toSearchParameters(params)
}
const url = new URL(baseUrl);
const url = new URL(baseUrl)
if (httpsOptions.port) {
url.port = httpsOptions.port.toString();
url.port = httpsOptions.port.toString()
}
if (httpsOptions.path) {
url.pathname = httpsOptions.path;
url.pathname = httpsOptions.path
}
let isJson = false;
let isJson = false
if (isPlainObject(body) || Array.isArray(body)) {
isJson = true;
body = JSON.stringify(body);
isJson = true
body = JSON.stringify(body)
}
const headers: Record<string, string> = {
...(httpsOptions.headers as any),
};
}
if (contentType) {
headers["Content-Type"] = contentType;
} else if (!httpsOptions.headers?.["Content-Type"] && isJson) {
headers["Content-Type"] = "application/json";
headers['Content-Type'] = contentType
} else if (!httpsOptions.headers?.['Content-Type'] && isJson) {
headers['Content-Type'] = 'application/json'
}

let retry = 0;
let retry = 0
while (true) {
try {
const response = await fetch(
Expand All @@ -222,27 +210,27 @@ export async function apiClient<Response>(
method,
agent: httpsOptions.agent,
body,
}),
);
})
)

const isSuccess = response.status >= 200 && response.status < 300;
const contentType = response.headers.get("content-type");
const isJsonResponse = contentType?.includes("application/json") ?? false;
const isSuccess = response.status >= 200 && response.status < 300
const contentType = response.headers.get('content-type')
const isJsonResponse = contentType?.includes('application/json') ?? false

if (isSuccess && isJsonResponse) {
return (await response.json()) as Response;
return (await response.json()) as Response
}

// helpful message for debugging
const text = await response.text();
if (response.status === 404 && text.includes("404 page not found")) {
const text = await response.text()
if (response.status === 404 && text.includes('404 page not found')) {
console.info(
`Did you forget to install your Custom Resources Definitions? path: ${httpsOptions.path}`,
);
`Did you forget to install your Custom Resources Definitions? path: ${httpsOptions.path}`
)
}
throw new Error(text);
throw new Error(text)
} catch (error: any) {
retry++;
retry++

if (
!(await options.retryCondition({
Expand All @@ -253,14 +241,14 @@ export async function apiClient<Response>(
options: options,
}))
) {
throw error;
throw error
}

await options.backoff(retry, options.maxRetries);
await options.backoff(retry, options.maxRetries)
}
}
}

const toSearchParameters = (parameters: Record<string, string>) => {
return new URLSearchParams(removeNullableProperties(parameters));
};
return new URLSearchParams(removeNullableProperties(parameters))
}

0 comments on commit e2a2578

Please sign in to comment.