1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

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>
This commit is contained in:
Saumya Shovan Roy (Deep) 2021-09-27 17:04:15 -04:00 committed by Jim Ehrismann
parent cc3f79a9b2
commit ef52e4e9e2
6 changed files with 211 additions and 52 deletions

View File

@ -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);

View File

@ -34,6 +34,7 @@ interface PortForwardArgs {
namespace: string;
name: string;
port: string;
forwardPort: string;
}
const internalPortRegex = /^forwarding from (?<address>.+) ->/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,44 +125,74 @@ class PortForward {
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, response, cluster } = request;
const { namespace, port, forwardPort, resourceType, resourceName } = params;
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 {
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) {
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}`
}, 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);
}
portForward.open();
respondJson(response, {port: portForward.internalPort});
} 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, response, cluster} = request;
const { namespace, port, forwardPort, resourceType, resourceName } = params;
respondJson(response, {});
const portForward = PortForward.getPortforward({
clusterId: cluster.id, kind: resourceType, name: resourceName,
namespace, port, forwardPort
});
respondJson(response, {port: portForward?.internalPort?? null});
}
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,
});
try {
await portForward.stop();
respondJson(response, {status: true});
} catch (error) {
logger.error(error);
}
}
}

View File

@ -35,9 +35,10 @@
text-decoration: underline;
}
.Spinner {
--spinner-size: #{$unit * 2};
margin-left: $margin;
position: absolute;
.portInput {
display: inline-block !important;
width: 70px;
margin-left: 10px;
margin-right: 10px;
}
}

View File

@ -28,20 +28,46 @@ 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<Props> {
@observable waiting = false;
@observable forwardPort = 0;
@observable isPortForwarded = false;
constructor(props: Props) {
super(props);
makeObservable(this);
this.init();
}
init() {
this.checkExistingPortForwarding().catch(error => {
console.error(error);
});
}
async checkExistingPortForwarding() {
const { service, port } = this.props;
const response = await apiBase.get<PortForwardResult>(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}/${this.forwardPort}`, {});
const activePort = response.port;
if (activePort) {
this.forwardPort = activePort;
this.isPortForwarded = true;
}
}
async portForward() {
@ -50,7 +76,25 @@ export class ServicePortComponent extends React.Component<Props> {
this.waiting = true;
try {
await apiBase.post(`/pods/${service.getNs()}/service/${service.getName()}/port-forward/${port.port}`, {});
const response = await apiBase.post<PortForwardResult>(`/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 +105,29 @@ export class ServicePortComponent extends React.Component<Props> {
render() {
const { port } = this.props;
const portForwardAction = async () => {
if (this.isPortForwarded) {
await this.stopPortForward();
}else {
await this.portForward();
}
};
return (
<div className={cssNames("ServicePortComponent", { waiting: this.waiting })}>
<span title="Open in a browser" onClick={() => this.portForward() }>
{port.toString()}
{this.waiting && (
<Spinner />
)}
</span>
{port.toString()}
{" "}
<text>to</text>
<Input className={"portInput"}
type="number"
min="0"
max="65535"
value= {this.forwardPort != 0? String(this.forwardPort) : ""}
disabled={this.isPortForwarded}
placeholder={"Random"}
onChange={(value) => this.forwardPort = Number(value)}
/>
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
</div>
);
}

View File

@ -36,9 +36,10 @@
position: relative;
}
.Spinner {
--spinner-size: #{$unit * 2};
margin-left: $margin;
position: absolute;
.portInput {
display: inline-block !important;
width: 70px;
margin-left: 10px;
margin-right: 10px;
}
}
}

View File

@ -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,38 @@ interface Props {
}
}
interface PortForwardResult {
port: number;
}
@observer
export class PodContainerPort extends React.Component<Props> {
@observable waiting = false;
@observable forwardPort = 0;
@observable isPortForwarded = false;
constructor(props: Props) {
super(props);
makeObservable(this);
this.init();
}
init() {
this.checkExistingPortForwarding().catch(error => {
console.error(error);
});
}
async checkExistingPortForwarding() {
const { pod, port } = this.props;
const response = await apiBase.get<PortForwardResult>(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}/${this.forwardPort}`, {});
const activePort = response.port;
if (activePort) {
this.forwardPort = activePort;
this.isPortForwarded = true;
}
}
async portForward() {
@ -54,7 +80,25 @@ export class PodContainerPort extends React.Component<Props> {
this.waiting = true;
try {
await apiBase.post(`/pods/${pod.getNs()}/pod/${pod.getName()}/port-forward/${port.containerPort}`, {});
const response = await apiBase.post<PortForwardResult>(`/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 +111,29 @@ export class PodContainerPort extends React.Component<Props> {
const { name, containerPort, protocol } = port;
const text = `${name ? `${name}: ` : ""}${containerPort}/${protocol}`;
const portForwardAction = async () => {
if (this.isPortForwarded) {
await this.stopPortForward();
} else {
await this.portForward();
}
};
return (
<div className={cssNames("PodContainerPort", { waiting: this.waiting })}>
<span title="Open in a browser" onClick={() => this.portForward() }>
{text}
{this.waiting && (
<Spinner />
)}
</span>
{text}
{" "}
<text>to</text>
<Input className={"portInput"}
type="number"
min="0"
max="65535"
value={this.forwardPort != 0? String(this.forwardPort) : ""}
disabled={this.isPortForwarded}
placeholder={"Random"}
onChange={(value) => this.forwardPort = Number(value)}
/>
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
</div>
);
}