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:
parent
cc3f79a9b2
commit
ef52e4e9e2
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user