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:
authored and GitHub committed 2021-10-07 15:51:26 -04:00
1 parent e10c13cdf4
commit 07c8177a97
22 files changed
+1137 -73

No files matched your search

+101 -42
View File
@@ -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);
}
}
}