mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Use JSON patch for editing components (#3674)
This commit is contained in:
1 parent
8b1de233f2
commit
4705a66632
18 files changed
+242
-106
No files matched your search
@@ -22,19 +22,27 @@
|
||||
import jsYaml from "js-yaml";
|
||||
import type { KubeJsonApiData } from "../kube-json-api";
|
||||
import { apiBase } from "../index";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
export const resourceApplierApi = {
|
||||
annotations: [
|
||||
"kubectl.kubernetes.io/last-applied-configuration"
|
||||
],
|
||||
export const annotations = [
|
||||
"kubectl.kubernetes.io/last-applied-configuration"
|
||||
];
|
||||
|
||||
async update(resource: object | string): Promise<KubeJsonApiData | null> {
|
||||
if (typeof resource === "string") {
|
||||
resource = jsYaml.safeLoad(resource);
|
||||
}
|
||||
|
||||
const [data = null] = await apiBase.post<KubeJsonApiData[]>("/stack", { data: resource });
|
||||
|
||||
return data;
|
||||
export async function update(resource: object | string): Promise<KubeJsonApiData> {
|
||||
if (typeof resource === "string") {
|
||||
resource = jsYaml.safeLoad(resource);
|
||||
}
|
||||
};
|
||||
|
||||
return apiBase.post<KubeJsonApiData>("/stack", { data: resource });
|
||||
}
|
||||
|
||||
export async function patch(name: string, kind: string, ns: string, patch: Patch): Promise<KubeJsonApiData> {
|
||||
return apiBase.patch<KubeJsonApiData>("/stack", {
|
||||
data: {
|
||||
name,
|
||||
kind,
|
||||
ns,
|
||||
patch,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import { parseKubeApi } from "./kube-api-parse";
|
||||
import type { KubeJsonApiData } from "./kube-json-api";
|
||||
import type { RequestInit } from "node-fetch";
|
||||
import AbortController from "abort-controller";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
export interface KubeObjectStoreLoadingParams<K extends KubeObject> {
|
||||
namespaces: string[];
|
||||
@@ -279,19 +280,29 @@ export abstract class KubeObjectStore<T extends KubeObject> extends ItemStore<T>
|
||||
return newItem;
|
||||
}
|
||||
|
||||
async update(item: T, data: Partial<T>): Promise<T> {
|
||||
const rawItem = await item.update(data);
|
||||
private postUpdate(rawItem: KubeJsonApiData): T {
|
||||
const newItem = new this.api.objectConstructor(rawItem);
|
||||
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
||||
|
||||
ensureObjectSelfLink(this.api, newItem);
|
||||
|
||||
const index = this.items.findIndex(item => item.getId() === newItem.getId());
|
||||
|
||||
this.items.splice(index, 1, newItem);
|
||||
if (index < 0) {
|
||||
this.items.push(newItem);
|
||||
} else {
|
||||
this.items[index] = newItem;
|
||||
}
|
||||
|
||||
return newItem;
|
||||
}
|
||||
|
||||
async patch(item: T, patch: Patch): Promise<T> {
|
||||
return this.postUpdate(await item.patch(patch));
|
||||
}
|
||||
|
||||
async update(item: T, data: Partial<T>): Promise<T> {
|
||||
return this.postUpdate(await item.update(data));
|
||||
}
|
||||
|
||||
async remove(item: T) {
|
||||
await item.delete();
|
||||
this.items.remove(item);
|
||||
|
||||
@@ -27,9 +27,9 @@ import { autoBind, formatDuration } from "../utils";
|
||||
import type { ItemObject } from "../item.store";
|
||||
import { apiKube } from "./index";
|
||||
import type { JsonApiParams } from "./json-api";
|
||||
import { resourceApplierApi } from "./endpoints/resource-applier.api";
|
||||
import * as resourceApplierApi from "./endpoints/resource-applier.api";
|
||||
import { hasOptionalProperty, hasTypedProperty, isObject, isString, bindPredicate, isTypedArray, isRecord } from "../../common/utils/type-narrowing";
|
||||
import _ from "lodash";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
export type KubeObjectConstructor<K extends KubeObject> = (new (data: KubeJsonApiData | any) => K) & {
|
||||
kind?: string;
|
||||
@@ -191,18 +191,28 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
return Object.entries(labels).map(([name, value]) => `${name}=${value}`);
|
||||
}
|
||||
|
||||
protected static readonly nonEditableFields = [
|
||||
"apiVersion",
|
||||
"kind",
|
||||
"metadata.name",
|
||||
"metadata.selfLink",
|
||||
"metadata.resourceVersion",
|
||||
"metadata.uid",
|
||||
"managedFields",
|
||||
"status",
|
||||
/**
|
||||
* These must be RFC6902 compliant paths
|
||||
*/
|
||||
private static readonly nonEditiablePathPrefixes = [
|
||||
"/metadata/managedFields",
|
||||
"/status",
|
||||
];
|
||||
private static readonly nonEditablePaths = new Set([
|
||||
"/apiVersion",
|
||||
"/kind",
|
||||
"/metadata/name",
|
||||
"/metadata/selfLink",
|
||||
"/metadata/resourceVersion",
|
||||
"/metadata/uid",
|
||||
...KubeObject.nonEditiablePathPrefixes,
|
||||
]);
|
||||
|
||||
constructor(data: KubeJsonApiData) {
|
||||
if (typeof data !== "object") {
|
||||
throw new TypeError(`Cannot create a KubeObject from ${typeof data}`);
|
||||
}
|
||||
|
||||
Object.assign(this, data);
|
||||
autoBind(this);
|
||||
}
|
||||
@@ -286,14 +296,31 @@ export class KubeObject<Metadata extends KubeObjectMetadata = KubeObjectMetadata
|
||||
return JSON.parse(JSON.stringify(this));
|
||||
}
|
||||
|
||||
// use unified resource-applier api for updating all k8s objects
|
||||
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
||||
for (const field of KubeObject.nonEditableFields) {
|
||||
if (!_.isEqual(_.get(this, field), _.get(data, field))) {
|
||||
throw new Error(`Failed to update Kube Object: ${field} has been modified`);
|
||||
async patch(patch: Patch): Promise<KubeJsonApiData | null> {
|
||||
for (const op of patch) {
|
||||
if (KubeObject.nonEditablePaths.has(op.path)) {
|
||||
throw new Error(`Failed to update ${this.kind}: JSON pointer ${op.path} has been modified`);
|
||||
}
|
||||
|
||||
for (const pathPrefix of KubeObject.nonEditiablePathPrefixes) {
|
||||
if (op.path.startsWith(`${pathPrefix}/`)) {
|
||||
throw new Error(`Failed to update ${this.kind}: Child JSON pointer of ${op.path} has been modified`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resourceApplierApi.patch(this.getName(), this.kind, this.getNs(), patch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a full update (or more specifically a replace)
|
||||
*
|
||||
* Note: this is brittle if `data` is not actually partial (but instead whole).
|
||||
* As fields such as `resourceVersion` will probably out of date. This is a
|
||||
* common race condition.
|
||||
*/
|
||||
async update(data: Partial<this>): Promise<KubeJsonApiData | null> {
|
||||
// use unified resource-applier api for updating all k8s objects
|
||||
return resourceApplierApi.update({
|
||||
...this.toPlainObject(),
|
||||
...data,
|
||||
|
||||
Reference in new issue
Block a user