mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Feature/port forward dashboard (#3922)
* Added ability to add custom port on pod and service port forwarding (#3295) * Added ability to add custom port on pod and service port forwarding Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com> * Added ability to add custom port on pod and service port forwarding - pod-container-port fixed init async and active port checking - service-port-component fixed init async and active port checking - port-forward-route promise and response fix Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com> * Added ability to add custom port on pod and service port forwarding Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com> * - Added Custom Port Selection for port forwarding - Implemented Random Port if custom port is not provided Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com> Co-authored-by: Saumya Shovan Roy <saumyashovanroy@gmail.com> * cherry-pick of hackweek work plus merge conflicts/build errors added a route to get all port forwards Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> Added Forwarded Ports to cluster dashboard Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> working port-forward page (open, edit, remove) Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> added local storage to the port-forward store Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> automatically restore port-forward after pod is restarted Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> start port-forwards using random local port by default, rearranged pod and service port-forward UI Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * refactor Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * more refactoring, don't always open port-forwards in browser, refined reused port-forward dialog Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * removed unimplemented forwarded port details page,modified logging Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * addressed some review comments Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * made port and forwardPort query params and cleaned up port-forward routing paths Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * address more review comments and change dashboard tab name to 'Port Forwarding' Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * changed port and forwardPort fields to be Numbers Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * removed extraneous reset() call, reorder field declarations Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * port-forward now gets the bundled kubectl path without going through the 'ensureKubectl' hoops Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * more cleanup/tweaking Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> * fix bug where port-forward info did not update on pod details page when different pod (in same deployment?) is clicked Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com> Co-authored-by: Saumya Shovan Roy (Deep) <12953177+rdeepc@users.noreply.github.com> Co-authored-by: Saumya Shovan Roy <saumyashovanroy@gmail.com>
This commit is contained in:
22 files changed
+1137
-73
No files matched your search
+5
-2
@@ -179,8 +179,11 @@ export class Router {
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/metrics` }, MetricsRoute.routeMetrics);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/metrics/providers` }, MetricsRoute.routeMetricsProviders);
|
||||
|
||||
// Port-forward API
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}` }, PortForwardRoute.routePortForward);
|
||||
// Port-forward API (the container port and local forwarding port are obtained from the query parameters)
|
||||
this.router.add({ method: "post", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routePortForward);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForward);
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/pods/port-forwards` }, PortForwardRoute.routeAllPortForwards);
|
||||
this.router.add({ method: "delete", path: `${apiPrefix}/pods/port-forward/{namespace}/{resourceType}/{resourceName}` }, PortForwardRoute.routeCurrentPortForwardStop);
|
||||
|
||||
// Helm API
|
||||
this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts);
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
import type { LensApiRequest } from "../router";
|
||||
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
|
||||
import { Kubectl } from "../kubectl";
|
||||
import { shell } from "electron";
|
||||
import * as tcpPortUsed from "tcp-port-used";
|
||||
import logger from "../logger";
|
||||
import { getPortFrom } from "../utils/get-port";
|
||||
@@ -33,7 +32,8 @@ interface PortForwardArgs {
|
||||
kind: string;
|
||||
namespace: string;
|
||||
name: string;
|
||||
port: string;
|
||||
port: number;
|
||||
forwardPort: number;
|
||||
}
|
||||
|
||||
const internalPortRegex = /^forwarding from (?<address>.+) ->/i;
|
||||
@@ -56,8 +56,8 @@ class PortForward {
|
||||
public kind: string;
|
||||
public namespace: string;
|
||||
public name: string;
|
||||
public port: string;
|
||||
public internalPort?: number;
|
||||
public port: number;
|
||||
public forwardPort: number;
|
||||
|
||||
constructor(public kubeConfig: string, args: PortForwardArgs) {
|
||||
this.clusterId = args.clusterId;
|
||||
@@ -65,16 +65,17 @@ class PortForward {
|
||||
this.namespace = args.namespace;
|
||||
this.name = args.name;
|
||||
this.port = args.port;
|
||||
this.forwardPort = args.forwardPort;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const kubectlBin = await Kubectl.bundled().getPath();
|
||||
const kubectlBin = await Kubectl.bundled().getPath(true);
|
||||
const args = [
|
||||
"--kubeconfig", this.kubeConfig,
|
||||
"port-forward",
|
||||
"-n", this.namespace,
|
||||
`${this.kind}/${this.name}`,
|
||||
`:${this.port}`
|
||||
`${this.forwardPort ?? ""}:${this.port}`
|
||||
];
|
||||
|
||||
this.process = spawn(kubectlBin, args, {
|
||||
@@ -89,12 +90,15 @@ class PortForward {
|
||||
}
|
||||
});
|
||||
|
||||
this.internalPort = await getPortFrom(this.process.stdout, {
|
||||
const internalPort = await getPortFrom(this.process.stdout, {
|
||||
lineRegex: internalPortRegex,
|
||||
});
|
||||
|
||||
try {
|
||||
await tcpPortUsed.waitUntilUsed(this.internalPort, 500, 15000);
|
||||
await tcpPortUsed.waitUntilUsed(internalPort, 500, 15000);
|
||||
|
||||
// make sure this.forwardPort is set to the actual port used (if it was 0 then an available port is found by 'kubectl port-forward')
|
||||
this.forwardPort = internalPort;
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -104,58 +108,113 @@ class PortForward {
|
||||
}
|
||||
}
|
||||
|
||||
public open() {
|
||||
shell.openExternal(`http://localhost:${this.internalPort}`)
|
||||
.catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, {
|
||||
clusterId: this.clusterId,
|
||||
port: this.port,
|
||||
kind: this.kind,
|
||||
namespace: this.namespace,
|
||||
name: this.name,
|
||||
}));
|
||||
public async stop() {
|
||||
this.process.kill();
|
||||
}
|
||||
}
|
||||
|
||||
export class PortForwardRoute {
|
||||
static async routePortForward(request: LensApiRequest) {
|
||||
const { params, response, cluster} = request;
|
||||
const { namespace, port, resourceType, resourceName } = params;
|
||||
let portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port
|
||||
});
|
||||
const { params, query, response, cluster } = request;
|
||||
const { namespace, resourceType, resourceName } = params;
|
||||
const port = Number(query.get("port"));
|
||||
const forwardPort = Number(query.get("forwardPort"));
|
||||
|
||||
if (!portForward) {
|
||||
logger.info(`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`);
|
||||
portForward = new PortForward(await cluster.getProxyKubeconfigPath(), {
|
||||
clusterId: cluster.id,
|
||||
kind: resourceType,
|
||||
namespace,
|
||||
name: resourceName,
|
||||
port,
|
||||
try {
|
||||
let portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port, forwardPort,
|
||||
});
|
||||
|
||||
try {
|
||||
let thePort = 0;
|
||||
|
||||
if (forwardPort > 0 && forwardPort < 65536) {
|
||||
thePort = forwardPort;
|
||||
}
|
||||
|
||||
if (!portForward) {
|
||||
logger.info(`Creating a new port-forward ${namespace}/${resourceType}/${resourceName}:${port}`);
|
||||
portForward = new PortForward(await cluster.getProxyKubeconfigPath(), {
|
||||
clusterId: cluster.id,
|
||||
kind: resourceType,
|
||||
namespace,
|
||||
name: resourceName,
|
||||
port,
|
||||
forwardPort: thePort,
|
||||
});
|
||||
|
||||
const started = await portForward.start();
|
||||
|
||||
if (!started) {
|
||||
logger.warn("[PORT-FORWARD-ROUTE]: failed to start a port-forward", { namespace, port, resourceType, resourceName });
|
||||
logger.error("[PORT-FORWARD-ROUTE]: failed to start a port-forward", { namespace, port, resourceType, resourceName });
|
||||
|
||||
return respondJson(response, {
|
||||
message: "Failed to open port-forward"
|
||||
message: `Failed to forward port ${port} to ${thePort ? forwardPort : "random port"}`
|
||||
}, 400);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName });
|
||||
|
||||
return respondJson(response, {
|
||||
message: error?.toString() || "Failed to open port-forward",
|
||||
}, 400);
|
||||
}
|
||||
|
||||
respondJson(response, { port: portForward.forwardPort });
|
||||
} catch (error) {
|
||||
logger.error(`[PORT-FORWARD-ROUTE]: failed to open a port-forward: ${error}`, { namespace, port, resourceType, resourceName });
|
||||
|
||||
return respondJson(response, {
|
||||
message: `Failed to forward port ${port}`
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
|
||||
portForward.open();
|
||||
static async routeCurrentPortForward(request: LensApiRequest) {
|
||||
const { params, query, response, cluster } = request;
|
||||
const { namespace, resourceType, resourceName } = params;
|
||||
const port = Number(query.get("port"));
|
||||
const forwardPort = Number(query.get("forwardPort"));
|
||||
|
||||
respondJson(response, {});
|
||||
const portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port, forwardPort
|
||||
});
|
||||
|
||||
respondJson(response, { port: portForward?.forwardPort ?? null });
|
||||
}
|
||||
|
||||
static async routeAllPortForwards(request: LensApiRequest) {
|
||||
const { response } = request;
|
||||
|
||||
const portForwards: PortForwardArgs[] = PortForward.portForwards.map(f => (
|
||||
{
|
||||
clusterId: f.clusterId,
|
||||
kind: f.kind,
|
||||
namespace: f.namespace,
|
||||
name: f.name,
|
||||
port: f.port,
|
||||
forwardPort: f.forwardPort,
|
||||
})
|
||||
);
|
||||
|
||||
respondJson(response, { portForwards });
|
||||
}
|
||||
|
||||
static async routeCurrentPortForwardStop(request: LensApiRequest) {
|
||||
const { params, query, response, cluster } = request;
|
||||
const { namespace, resourceType, resourceName } = params;
|
||||
const port = Number(query.get("port"));
|
||||
const forwardPort = Number(query.get("forwardPort"));
|
||||
|
||||
const portForward = PortForward.getPortforward({
|
||||
clusterId: cluster.id, kind: resourceType, name: resourceName,
|
||||
namespace, port, forwardPort,
|
||||
});
|
||||
|
||||
try {
|
||||
await portForward.stop();
|
||||
respondJson(response, { status: true });
|
||||
} catch (error) {
|
||||
logger.error("[PORT-FORWARD-ROUTE]: error stopping a port-forward", { namespace, port, forwardPort, resourceType, resourceName });
|
||||
|
||||
return respondJson(response, {
|
||||
message: `error stopping a forward port ${port}`
|
||||
}, 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user