K8s/Typescript: Support generic status for ScopedResourceClient (#98509)

This commit is contained in:
Ryan McKinley 2025-01-07 09:26:00 +03:00 committed by GitHub
parent addc1c95a5
commit 322c7d9548
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 49 deletions

View File

@ -21,7 +21,7 @@ export interface GroupVersionResource {
resource: string;
}
export class ScopedResourceClient<T = object, K = string> implements ResourceClient<T, K> {
export class ScopedResourceClient<T = object, S = object, K = string> implements ResourceClient<T, S, K> {
readonly url: string;
constructor(gvr: GroupVersionResource, namespaced = true) {
@ -30,23 +30,23 @@ export class ScopedResourceClient<T = object, K = string> implements ResourceCli
this.url = `/apis/${gvr.group}/${gvr.version}/${ns}${gvr.resource}`;
}
public async get(name: string): Promise<Resource<T, K>> {
return getBackendSrv().get<Resource<T, K>>(`${this.url}/${name}`);
public async get(name: string): Promise<Resource<T, S, K>> {
return getBackendSrv().get<Resource<T, S, K>>(`${this.url}/${name}`);
}
public async subresource<S>(name: string, path: string): Promise<S> {
return getBackendSrv().get<S>(`${this.url}/${name}/${path}`);
}
public async list(opts?: ListOptions | undefined): Promise<ResourceList<T, K>> {
public async list(opts?: ListOptions | undefined): Promise<ResourceList<T, S, K>> {
const finalOpts = opts || {};
finalOpts.labelSelector = this.parseListOptionsSelector(finalOpts?.labelSelector);
finalOpts.fieldSelector = this.parseListOptionsSelector(finalOpts?.fieldSelector);
return getBackendSrv().get<ResourceList<T, K>>(this.url, opts);
return getBackendSrv().get<ResourceList<T, S, K>>(this.url, opts);
}
public async create(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>> {
public async create(obj: ResourceForCreate<T, K>): Promise<Resource<T, S, K>> {
if (!obj.metadata.name && !obj.metadata.generateName) {
const login = contextSrv.user.login;
// GenerateName lets the apiserver create a new uid for the name
@ -57,47 +57,16 @@ export class ScopedResourceClient<T = object, K = string> implements ResourceCli
return getBackendSrv().post(this.url, obj);
}
public async update(obj: Resource<T, K>): Promise<Resource<T, K>> {
public async update(obj: Resource<T, S, K>): Promise<Resource<T, S, K>> {
setSavedFromUIAnnotation(obj.metadata);
return getBackendSrv().put<Resource<T, K>>(`${this.url}/${obj.metadata.name}`, obj);
return getBackendSrv().put<Resource<T, S, K>>(`${this.url}/${obj.metadata.name}`, obj);
}
public async delete(name: string): Promise<MetaStatus> {
return getBackendSrv().delete<MetaStatus>(`${this.url}/${name}`);
}
private parseListOptionsSelector(
selector: ListOptionsLabelSelector | ListOptionsFieldSelector | undefined
): string | undefined {
if (!Array.isArray(selector)) {
return selector;
}
return selector
.map((label) => {
const key = String(label.key);
const operator = label.operator;
switch (operator) {
case '=':
case '!=':
return `${key}${operator}${label.value}`;
case 'in':
case 'notin':
return `${key} ${operator} (${label.value.join(',')})`;
case '':
case '!':
return `${operator}${key}`;
default:
return null;
}
})
.filter(Boolean)
.join(',');
}
private parseListOptionsSelector = parseListOptionsSelector;
}
// add the origin annotations so we know what was set from the UI
@ -138,3 +107,34 @@ export class DatasourceAPIVersions {
return apiVersions[pluginID];
}
}
export const parseListOptionsSelector = (selector: ListOptionsLabelSelector | ListOptionsFieldSelector | undefined) => {
if (!Array.isArray(selector)) {
return selector;
}
return selector
.map((label) => {
const key = String(label.key);
const operator = label.operator;
switch (operator) {
case '=':
case '!=':
return `${key}${operator}${label.value}`;
case 'in':
case 'notin':
return `${key} ${operator} (${label.value.join(',')})`;
case '':
case '!':
return `${operator}${key}`;
default:
return null;
}
})
.filter(Boolean)
.join(',');
};

View File

@ -84,9 +84,10 @@ type GrafanaClientAnnotations = {
[AnnoKeyDashboardGnetId]?: string;
};
export interface Resource<T = object, K = string> extends TypeMeta<K> {
export interface Resource<T = object, S = object, K = string> extends TypeMeta<K> {
metadata: ObjectMeta;
spec: T;
status?: S;
}
export interface ResourceForCreate<T = object, K = string> extends Partial<TypeMeta<K>> {
@ -103,9 +104,9 @@ export interface ListMeta {
remainingItemCount?: number;
}
export interface ResourceList<T, K = string> extends TypeMeta {
export interface ResourceList<T, S = object, K = string> extends TypeMeta {
metadata: ListMeta;
items: Array<Resource<T, K>>;
items: Array<Resource<T, S, K>>;
}
export type ListOptionsLabelSelector =
@ -168,12 +169,12 @@ export interface MetaStatus {
details?: object;
}
export interface ResourceClient<T = object, K = string> {
create(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>>;
get(name: string): Promise<Resource<T, K>>;
export interface ResourceClient<T = object, S = object, K = string> {
create(obj: ResourceForCreate<T, K>): Promise<Resource<T, S, K>>;
get(name: string): Promise<Resource<T, S, K>>;
subresource<S>(name: string, path: string): Promise<S>;
list(opts?: ListOptions): Promise<ResourceList<T, K>>;
update(obj: ResourceForCreate<T, K>): Promise<Resource<T, K>>;
list(opts?: ListOptions): Promise<ResourceList<T, S, K>>;
update(obj: ResourceForCreate<T, K>): Promise<Resource<T, S, K>>;
delete(name: string): Promise<MetaStatus>;
}

View File

@ -12,7 +12,7 @@ const namespace = config.namespace ?? 'default';
const nodesEndpoint = `/apis/${group}/${version}/namespaces/${namespace}/find/scope_node_children`;
const dashboardsEndpoint = `/apis/${group}/${version}/namespaces/${namespace}/find/scope_dashboard_bindings`;
const scopesClient = new ScopedResourceClient<ScopeSpec, 'Scope'>({
const scopesClient = new ScopedResourceClient<ScopeSpec, unknown, 'Scope'>({
group,
version,
resource: 'scopes',