From 015bb0821b275bc0dcf286e958f7a298e45d1be7 Mon Sep 17 00:00:00 2001 From: rdeepc <12953177+rdeepc@users.noreply.github.com> Date: Mon, 5 Jul 2021 23:03:06 -0400 Subject: [PATCH] Added ability to add custom port on pod and service port forwarding Signed-off-by: rdeepc <12953177+rdeepc@users.noreply.github.com> --- src/main/router.ts | 4 +- src/main/routes/port-forward-route.ts | 89 ++++++++++++++----- .../service-port-component.scss | 9 +- .../service-port-component.tsx | 77 ++++++++++++++-- .../+workloads-pods/pod-container-port.scss | 11 +-- .../+workloads-pods/pod-container-port.tsx | 76 ++++++++++++++-- 6 files changed, 219 insertions(+), 47 deletions(-) diff --git a/src/main/router.ts b/src/main/router.ts index 50175aa9de..16fed03734 100644 --- a/src/main/router.ts +++ b/src/main/router.ts @@ -180,7 +180,9 @@ export class Router { 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); + this.router.add({ method: "post", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routePortForward); + this.router.add({ method: "get", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routeCurrentPortForward); + this.router.add({ method: "delete", path: `${apiPrefix}/pods/{namespace}/{resourceType}/{resourceName}/port-forward/{port}/{forwardPort}` }, PortForwardRoute.routeCurrentPortForwardStop); // Helm API this.router.add({ method: "get", path: `${apiPrefix}/v2/charts` }, HelmApiRoute.listCharts); diff --git a/src/main/routes/port-forward-route.ts b/src/main/routes/port-forward-route.ts index cfd88e771f..f4253a3571 100644 --- a/src/main/routes/port-forward-route.ts +++ b/src/main/routes/port-forward-route.ts @@ -34,6 +34,7 @@ interface PortForwardArgs { namespace: string; name: string; port: string; + forwardPort: string; } const internalPortRegex = /^forwarding from (?
.+) ->/i; @@ -58,6 +59,7 @@ class PortForward { public name: string; public port: string; public internalPort?: number; + public forwardPort: string; constructor(public kubeConfig: string, args: PortForwardArgs) { this.clusterId = args.clusterId; @@ -65,6 +67,7 @@ class PortForward { this.namespace = args.namespace; this.name = args.name; this.port = args.port; + this.forwardPort = args.forwardPort; } public async start() { @@ -74,7 +77,7 @@ class PortForward { "port-forward", "-n", this.namespace, `${this.kind}/${this.name}`, - `:${this.port}` + `${this.forwardPort??""}:${this.port}` ]; this.process = spawn(kubectlBin, args, { @@ -104,6 +107,10 @@ class PortForward { } } + public async stop() { + this.process.kill(); + } + public open() { shell.openExternal(`http://localhost:${this.internalPort}`) .catch(error => logger.error(`[PORT-FORWARD]: failed to open external shell: ${error}`, { @@ -118,33 +125,73 @@ class PortForward { export class PortForwardRoute { static async routePortForward(request: LensApiRequest) { + const { params, response, cluster } = request; + const { namespace, port, forwardPort, resourceType, resourceName } = params; + + try { + let portForward = PortForward.getPortforward({ + clusterId: cluster.id, kind: resourceType, name: resourceName, + namespace, port, 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 + }); + + const started = await portForward.start(); + + if (!started) { + return respondJson(response, { + message: `Failed to forward port ${port} to ${forwardPort}` + }, 400); + } + } + + portForward.open(); + respondJson(response, {port: portForward.forwardPort}); + } catch (e) { + return respondJson(response, { + message: `Failed to forward port ${port} to ${forwardPort}` + }, 400); + } + } + + static async routeCurrentPortForward(request: LensApiRequest) { const { params, response, cluster} = request; - const { namespace, port, resourceType, resourceName } = params; - let portForward = PortForward.getPortforward({ + const { namespace, port, forwardPort, resourceType, resourceName } = params; + + const portForward = PortForward.getPortforward({ clusterId: cluster.id, kind: resourceType, name: resourceName, - namespace, port + namespace, port, 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, - }); - const started = await portForward.start(); + let forwardedPort = -1; - if (!started) { - return respondJson(response, { - message: "Failed to open port-forward" - }, 400); - } + if (portForward) { + forwardedPort = Number(portForward.forwardPort); } - portForward.open(); + respondJson(response, {port: forwardedPort}); + } - respondJson(response, {}); + static async routeCurrentPortForwardStop(request: LensApiRequest) { + const { params, response, cluster} = request; + const { namespace, port, forwardPort, resourceType, resourceName } = params; + + const portForward = PortForward.getPortforward({ + clusterId: cluster.id, kind: resourceType, name: resourceName, + namespace, port, forwardPort, + }); + + portForward.stop().then( () => { + respondJson(response, {status: true}); + }).catch(error => logger.error(error)); } } diff --git a/src/renderer/components/+network-services/service-port-component.scss b/src/renderer/components/+network-services/service-port-component.scss index 89369f560f..78153122e8 100644 --- a/src/renderer/components/+network-services/service-port-component.scss +++ b/src/renderer/components/+network-services/service-port-component.scss @@ -35,9 +35,10 @@ text-decoration: underline; } - .Spinner { - --spinner-size: #{$unit * 2}; - margin-left: $margin; - position: absolute; + .portInput { + display: inline-block !important; + width: 65px; + margin-left: 10px; + margin-right: 10px; } } diff --git a/src/renderer/components/+network-services/service-port-component.tsx b/src/renderer/components/+network-services/service-port-component.tsx index a0c4c32f49..864cec3d8b 100644 --- a/src/renderer/components/+network-services/service-port-component.tsx +++ b/src/renderer/components/+network-services/service-port-component.tsx @@ -28,20 +28,44 @@ import { apiBase } from "../../api"; import { observable, makeObservable } from "mobx"; import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; -import { Spinner } from "../spinner"; +import { Input } from "../input"; +import { Button } from "../button"; interface Props { service: Service; port: ServicePort; } +interface PortForwardResult { + port: number; +} + @observer export class ServicePortComponent extends React.Component { @observable waiting = false; + @observable forwardPort = -1; + @observable isPortForwarded = false; constructor(props: Props) { super(props); makeObservable(this); + this.init(); + } + + init() { + this.checkExistingPortForwarding().then(); + } + + async checkExistingPortForwarding() { + const { service, port } = this.props; + const response = await apiBase.get(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {}); + + const activePort = response.port; + + if (activePort && activePort != -1) { + this.forwardPort = activePort; + this.isPortForwarded = true; + } } async portForward() { @@ -50,7 +74,26 @@ export class ServicePortComponent extends React.Component { this.waiting = true; try { - await apiBase.post(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}`, {}); + const response = await apiBase.post(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {}); + + this.forwardPort = response.port; + this.isPortForwarded = true; + + } catch(error) { + Notifications.error(error); + } finally { + this.waiting = false; + } + } + + async stopPortForward() { + const { service, port } = this.props; + + this.waiting = true; + + try { + await apiBase.del(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {}); + this.isPortForwarded = false; } catch(error) { Notifications.error(error); } finally { @@ -61,14 +104,32 @@ export class ServicePortComponent extends React.Component { render() { const { port } = this.props; + if (this.forwardPort == -1) { + this.forwardPort = port.port; + } + + const portForwardAction = async () => { + if (this.isPortForwarded) { + await this.stopPortForward(); + }else { + await this.portForward(); + } + }; + return (
- this.portForward() }> - {port.toString()} - {this.waiting && ( - - )} - + {port.toString()} + {" "} + to + this.forwardPort = Number(value)} + /> +
); } diff --git a/src/renderer/components/+workloads-pods/pod-container-port.scss b/src/renderer/components/+workloads-pods/pod-container-port.scss index 80d2425aac..89b224077f 100644 --- a/src/renderer/components/+workloads-pods/pod-container-port.scss +++ b/src/renderer/components/+workloads-pods/pod-container-port.scss @@ -36,9 +36,10 @@ position: relative; } - .Spinner { - --spinner-size: #{$unit * 2}; - margin-left: $margin; - position: absolute; + .portInput { + display: inline-block !important; + width: 65px; + margin-left: 10px; + margin-right: 10px; } -} \ No newline at end of file +} diff --git a/src/renderer/components/+workloads-pods/pod-container-port.tsx b/src/renderer/components/+workloads-pods/pod-container-port.tsx index 9e035c1fd9..9c5e670bab 100644 --- a/src/renderer/components/+workloads-pods/pod-container-port.tsx +++ b/src/renderer/components/+workloads-pods/pod-container-port.tsx @@ -28,7 +28,8 @@ import { apiBase } from "../../api"; import { observable, makeObservable } from "mobx"; import { cssNames } from "../../utils"; import { Notifications } from "../notifications"; -import { Spinner } from "../spinner"; +import { Button } from "../button"; +import { Input } from "../input"; interface Props { pod: Pod; @@ -39,13 +40,36 @@ interface Props { } } +interface PortForwardResult { + port: number; +} + @observer export class PodContainerPort extends React.Component { @observable waiting = false; + @observable forwardPort = -1; + @observable isPortForwarded = false; constructor(props: Props) { super(props); makeObservable(this); + this.init(); + } + + init() { + this.checkExistingPortForwarding().then(); + } + + async checkExistingPortForwarding() { + const { pod, port } = this.props; + const response = await apiBase.get(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {}); + + const activePort = response.port; + + if (activePort && activePort != -1) { + this.forwardPort = activePort; + this.isPortForwarded = true; + } } async portForward() { @@ -54,7 +78,25 @@ export class PodContainerPort extends React.Component { this.waiting = true; try { - await apiBase.post(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}`, {}); + const response = await apiBase.post(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {}); + + this.forwardPort = response.port; + this.isPortForwarded = true; + } catch(error) { + Notifications.error(error); + } finally { + this.waiting = false; + } + } + + async stopPortForward() { + const { pod, port } = this.props; + + this.waiting = true; + + try { + await apiBase.del(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {}); + this.isPortForwarded = false; } catch(error) { Notifications.error(error); } finally { @@ -67,14 +109,32 @@ export class PodContainerPort extends React.Component { const { name, containerPort, protocol } = port; const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`; + if (this.forwardPort == -1) { + this.forwardPort = containerPort; + } + + const portForwardAction = async () => { + if (this.isPortForwarded) { + await this.stopPortForward(); + }else { + await this.portForward(); + } + }; + return (
- this.portForward() }> - {text} - {this.waiting && ( - - )} - + {text} + {" "} + to + this.forwardPort = Number(value)} + /> +
); }