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

This commit is contained in:
rdeepc 2021-07-05 23:03:06 -04:00
parent 8ac3203924
commit 6f86615a91
6 changed files with 219 additions and 47 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,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));
}
}

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: 65px;
margin-left: 10px;
margin-right: 10px;
}
}

View File

@ -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<Props> {
@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<PortForwardResult>(`/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<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 +104,32 @@ export class ServicePortComponent extends React.Component<Props> {
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 (
<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= {String(this.forwardPort)}
disabled={this.isPortForwarded}
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: 65px;
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,36 @@ interface Props {
}
}
interface PortForwardResult {
port: number;
}
@observer
export class PodContainerPort extends React.Component<Props> {
@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<PortForwardResult>(`/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<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 +109,32 @@ export class PodContainerPort extends React.Component<Props> {
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 (
<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= {String(this.forwardPort)}
disabled={this.isPortForwarded}
onChange={(value) => this.forwardPort = Number(value)}
/>
<Button onClick={() => portForwardAction()}> {this.isPortForwarded ? "Stop":"Forward"} </Button>
</div>
);
}