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,55 +22,96 @@
|
||||
import type { Cluster } from "./cluster";
|
||||
import type { KubernetesObject } from "@kubernetes/client-node";
|
||||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import fs from "fs-extra";
|
||||
import * as yaml from "js-yaml";
|
||||
import path from "path";
|
||||
import * as tempy from "tempy";
|
||||
import logger from "./logger";
|
||||
import { appEventBus } from "../common/event-bus";
|
||||
import { cloneJsonObject } from "../common/utils";
|
||||
import type { Patch } from "rfc6902";
|
||||
import { promiseExecFile } from "./promise-exec";
|
||||
|
||||
export class ResourceApplier {
|
||||
constructor(protected cluster: Cluster) {
|
||||
constructor(protected cluster: Cluster) {}
|
||||
|
||||
/**
|
||||
* Patch a kube resource's manifest, throwing any error that occurs.
|
||||
* @param name The name of the kube resource
|
||||
* @param kind The kind of the kube resource
|
||||
* @param patch The list of JSON operations
|
||||
* @param ns The optional namespace of the kube resource
|
||||
*/
|
||||
async patch(name: string, kind: string, patch: Patch, ns?: string): Promise<string> {
|
||||
appEventBus.emit({ name: "resource", action: "patch" });
|
||||
|
||||
const kubectl = await this.cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
||||
const args = [
|
||||
"--kubeconfig", proxyKubeconfigPath,
|
||||
"patch",
|
||||
kind,
|
||||
name,
|
||||
];
|
||||
|
||||
if (ns) {
|
||||
args.push("--namespace", ns);
|
||||
}
|
||||
|
||||
args.push(
|
||||
"--type", "json",
|
||||
"--patch", JSON.stringify(patch),
|
||||
"-o", "json"
|
||||
);
|
||||
|
||||
try {
|
||||
const { stdout } = await promiseExecFile(kubectlPath, args);
|
||||
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error.stderr ?? error;
|
||||
}
|
||||
}
|
||||
|
||||
async apply(resource: KubernetesObject | any): Promise<string> {
|
||||
resource = this.sanitizeObject(resource);
|
||||
appEventBus.emit({ name: "resource", action: "apply" });
|
||||
|
||||
return await this.kubectlApply(yaml.safeDump(resource));
|
||||
return this.kubectlApply(yaml.safeDump(resource));
|
||||
}
|
||||
|
||||
protected async kubectlApply(content: string): Promise<string> {
|
||||
const kubectl = await this.cluster.ensureKubectl();
|
||||
const kubectlPath = await kubectl.getPath();
|
||||
const proxyKubeconfigPath = await this.cluster.getProxyKubeconfigPath();
|
||||
const fileName = tempy.file({ name: "resource.yaml" });
|
||||
const args = [
|
||||
"apply",
|
||||
"--kubeconfig", proxyKubeconfigPath,
|
||||
"-o", "json",
|
||||
"-f", fileName,
|
||||
];
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const fileName = tempy.file({ name: "resource.yaml" });
|
||||
logger.debug(`shooting manifests with ${kubectlPath}`, { args });
|
||||
|
||||
fs.writeFileSync(fileName, content);
|
||||
const cmd = `"${kubectlPath}" apply --kubeconfig "${proxyKubeconfigPath}" -o json -f "${fileName}"`;
|
||||
const execEnv = { ...process.env };
|
||||
const httpsProxy = this.cluster.preferences?.httpsProxy;
|
||||
|
||||
logger.debug(`shooting manifests with: ${cmd}`);
|
||||
const execEnv: NodeJS.ProcessEnv = Object.assign({}, process.env);
|
||||
const httpsProxy = this.cluster.preferences?.httpsProxy;
|
||||
if (httpsProxy) {
|
||||
execEnv.HTTPS_PROXY = httpsProxy;
|
||||
}
|
||||
|
||||
if (httpsProxy) {
|
||||
execEnv["HTTPS_PROXY"] = httpsProxy;
|
||||
}
|
||||
exec(cmd, { env: execEnv },
|
||||
(error, stdout, stderr) => {
|
||||
if (stderr != "") {
|
||||
fs.unlinkSync(fileName);
|
||||
reject(stderr);
|
||||
try {
|
||||
await fs.writeFile(fileName, content);
|
||||
const { stdout } = await promiseExecFile(kubectlPath, args);
|
||||
|
||||
return;
|
||||
}
|
||||
fs.unlinkSync(fileName);
|
||||
resolve(JSON.parse(stdout));
|
||||
});
|
||||
});
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
throw error?.stderr ?? error;
|
||||
} finally {
|
||||
await fs.unlink(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public async kubectlApplyAll(resources: string[], extraArgs = ["-o", "json"]): Promise<string> {
|
||||
|
||||
@@ -198,5 +198,6 @@ export class Router {
|
||||
|
||||
// Resource Applier API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.applyResource);
|
||||
this.router.add({ method: "patch", path: `${apiPrefix}/stack` }, ResourceApplierApiRoute.patchResource);
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,19 @@ export class ResourceApplierApiRoute {
|
||||
try {
|
||||
const resource = await new ResourceApplier(cluster).apply(payload);
|
||||
|
||||
respondJson(response, [resource], 200);
|
||||
respondJson(response, resource, 200);
|
||||
} catch (error) {
|
||||
respondText(response, error, 422);
|
||||
}
|
||||
}
|
||||
|
||||
static async patchResource(request: LensApiRequest) {
|
||||
const { response, cluster, payload } = request;
|
||||
|
||||
try {
|
||||
const resource = await new ResourceApplier(cluster).patch(payload.name, payload.kind, payload.patch, payload.ns);
|
||||
|
||||
respondJson(response, resource, 200);
|
||||
} catch (error) {
|
||||
respondText(response, error, 422);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,37 @@
|
||||
|
||||
import type http from "http";
|
||||
|
||||
export function respondJson(res: http.ServerResponse, content: any, status = 200) {
|
||||
respond(res, JSON.stringify(content), "application/json", status);
|
||||
/**
|
||||
* Respond to a HTTP request with a body of JSON data
|
||||
* @param res The HTTP response to write data to
|
||||
* @param content The data or its JSON stringified version of it
|
||||
* @param status [200] The status code to respond with
|
||||
*/
|
||||
export function respondJson(res: http.ServerResponse, content: Object | string, status = 200) {
|
||||
const normalizedContent = typeof content === "object"
|
||||
? JSON.stringify(content)
|
||||
: content;
|
||||
|
||||
respond(res, normalizedContent, "application/json", status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a HTTP request with a body of plain text data
|
||||
* @param res The HTTP response to write data to
|
||||
* @param content The string data to respond with
|
||||
* @param status [200] The status code to respond with
|
||||
*/
|
||||
export function respondText(res: http.ServerResponse, content: string, status = 200) {
|
||||
respond(res, content, "text/plain", status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a HTTP request with a body of plain text data
|
||||
* @param res The HTTP response to write data to
|
||||
* @param content The string data to respond with
|
||||
* @param contentType The HTTP Content-Type header value
|
||||
* @param status [200] The status code to respond with
|
||||
*/
|
||||
export function respond(res: http.ServerResponse, content: string, contentType: string, status = 200) {
|
||||
res.setHeader("Content-Type", contentType);
|
||||
res.statusCode = status;
|
||||
|
||||
Reference in new issue
Block a user