Use JSON patch for editing components (#3674)

This commit is contained in:
Sebastian Malton authored and GitHub committed 2021-10-04 16:28:19 -04:00
1 parent 8b1de233f2
commit 4705a66632
18 files changed
+242 -106

No files matched your search

+65 -24
View File
@@ -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> {
+1
View File
@@ -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);
}
}
+13 -1
View File
@@ -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);
}
+25 -2
View File
@@ -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;