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

add continuation after extension install, add notification of install

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2020-12-09 14:45:05 -05:00
parent 0a44260cd5
commit b6a17eb1be
8 changed files with 97 additions and 46 deletions

View File

@ -337,7 +337,14 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
}
});
this.activeCluster = newClusters.has(activeCluster) ? activeCluster : null;
const curActiveCluster = newClusters.get(activeCluster);
if (!curActiveCluster || curActiveCluster.workspace !== workspaceStore.currentWorkspaceId) {
this.activeCluster = null;
} else {
this.activeCluster = activeCluster;
}
this.clusters.replace(newClusters);
this.removedClusters.replace(removedClusters);
}

View File

@ -131,6 +131,7 @@ app
event.preventDefault();
// Broadcast "open-url" events to renderer
console.log(`broadcasting: ${rawUrl}`);
broadcastMessage(lensProtocolChannel, { rawUrl });
});

View File

@ -10,6 +10,7 @@ import { Router } from "./router";
import { ClusterManager } from "./cluster-manager";
import { ContextHandler } from "./context-handler";
import logger from "./logger";
import { chunk } from "lodash";
export class LensProxy {
protected origin: string;
@ -65,10 +66,11 @@ export class LensProxy {
return spdyProxy;
}
private static readonly ProxyFilterHeaders = new Set(["Host", "Authorization"]);
protected async handleProxyUpgrade(proxy: httpProxy, req: http.IncomingMessage, socket: net.Socket, head: Buffer) {
const cluster = this.clusterManager.getClusterForRequest(req);
if (cluster) {
if (cluster?.initialized) {
const proxyUrl = await cluster.contextHandler.resolveAuthProxyUrl() + req.url.replace(apiKubePrefix, "");
const apiUrl = url.parse(cluster.apiUrl);
const pUrl = url.parse(proxyUrl);
@ -79,13 +81,14 @@ export class LensProxy {
proxySocket.write(`${req.method} ${pUrl.path} HTTP/1.1\r\n`);
proxySocket.write(`Host: ${apiUrl.host}\r\n`);
for (let i = 0; i < req.rawHeaders.length; i += 2) {
const key = req.rawHeaders[i];
if (key !== "Host" && key !== "Authorization") {
proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i+1]}\r\n`);
for (const [key, value] of chunk(req.rawHeaders, 2)) {
if (LensProxy.ProxyFilterHeaders.has(key)) {
continue;
}
proxySocket.write(`${key}: ${value}\r\n`);
}
proxySocket.write("\r\n");
proxySocket.write(head);
});
@ -182,13 +185,19 @@ export class LensProxy {
const cluster = this.clusterManager.getClusterForRequest(req);
if (cluster) {
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
if (cluster.initialized) {
const proxyTarget = await this.getProxyTarget(req, cluster.contextHandler);
if (proxyTarget) {
// allow to fetch apis in "clusterId.localhost:port" from "localhost:port"
res.setHeader("Access-Control-Allow-Origin", this.origin);
if (proxyTarget) {
// allow to fetch apis in "clusterId.localhost:port" from "localhost:port"
res.setHeader("Access-Control-Allow-Origin", this.origin);
return proxy.web(req, res, proxyTarget);
return proxy.web(req, res, proxyTarget);
}
} else {
res.statusCode = 404;
return res.end("Not initialized");
}
}
this.router.route(cluster, req, res);

View File

@ -60,19 +60,23 @@ interface ExtensionUrlMatch {
export class LensProtocolRouter extends Singleton {
private extentionRoutes = new Map<ExtensionId, Map<string, RouteHandler>>();
private internalRoutes = new Map<string, RouteHandler>();
private missingExtensionHandler?: (name: string) => Promise<void>;
private missingExtensionHandler?: (name: string) => Promise<boolean>;
private static readonly LoggingPrefix = "[PROTOCOL ROUTER]";
private static ExtensionUrlSchema = `/:${EXTENSION_PUBLISHER_MATCH}/:${EXTENSION_NAME_MATCH}`;
public init() {
subscribeToBroadcast(lensProtocolChannel, ((_event, { rawUrl }) => {
console.log(`receiving: ${rawUrl}`);
try {
this.route(Url(rawUrl, true));
} catch (error) {
if (error instanceof RoutingError) {
logger.error(`[PROTOCOL ROUTER]: ${error}`, { url: error.url });
logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { url: error.url });
} else {
logger.error(`[PROTOCOL ROUTER]: ${error}`, { rawUrl });
logger.error(`${LensProtocolRouter.LoggingPrefix}: ${error}`, { rawUrl });
}
}
}));
@ -81,7 +85,7 @@ export class LensProtocolRouter extends Singleton {
/**
* route
*/
public route(url: Url): void {
public async route(url: Url): Promise<void> {
if (url.protocol.toLowerCase() !== "lens:") {
throw new RoutingError(RoutingErrorType.INVALID_PROTOCOL, url);
}
@ -90,8 +94,7 @@ export class LensProtocolRouter extends Singleton {
case "internal":
return this._route(this.internalRoutes, url);
case "extension":
// Possible rejected promise is ignored
this._routeToExtension(url);
return this._routeToExtension(url);
default:
throw new RoutingError(RoutingErrorType.INVALID_HOST, url);
@ -108,17 +111,23 @@ export class LensProtocolRouter extends Singleton {
const { [EXTENSION_PUBLISHER_MATCH]: publisher, [EXTENSION_NAME_MATCH]: partialName } = match.params;
const name = `${publisher}/${partialName}`;
logger.info(`[PROTOCOL ROUTER] Extension ${name} matched`);
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched`);
const routes = this.extentionRoutes.get(name);
let routes = this.extentionRoutes.get(name);
if (!routes) {
if (this.missingExtensionHandler) {
await this.missingExtensionHandler(name);
if (!await this.missingExtensionHandler(name)) {
return;
}
// TODO: After installation we can continue to route to the extension..
// but this is difficult, since the promise resolves before extension installation is complete.
return;
routes = this.extentionRoutes.get(name);
if (!routes) {
logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but has no routes`);
return;
}
} else {
throw new RoutingError(RoutingErrorType.MISSING_EXTENSION, url);
}
@ -146,6 +155,8 @@ export class LensProtocolRouter extends Singleton {
throw new RoutingError(RoutingErrorType.NO_HANDLER, url);
}
logger.info(`${LensProtocolRouter.LoggingPrefix}: routing ${url.toString()}`);
const [match, handler] = route;
delete match.params[EXTENSION_NAME_MATCH];
@ -174,7 +185,11 @@ export class LensProtocolRouter extends Singleton {
this.extentionRoutes.get(id).set(urlSchema, handler);
}
public onMissingExtension(handler: (name: string) => Promise<void>) {
/**
* onMissingExtension registers the handler for when an extension is missing
* @param handler If the called handler resolves to true then the routes will be tried again
*/
public onMissingExtension(handler: (name: string) => Promise<boolean>) {
this.missingExtensionHandler = handler;
}
}

View File

@ -1,15 +1,18 @@
import logger from "../../../main/logger";
import { RouteParams } from "../../../main/protocol-handler";
import { installFromNpm } from "../../components/+extensions";
import { extensionsURL, installFromNpm } from "../../components/+extensions";
import { navigate } from "../../navigation";
export async function installExtension(params: RouteParams): Promise<void> {
const { name } = params.search;
if (!name) {
return void logger.info("installExtension handler: missing 'name' from search params");
}
try {
navigate(extensionsURL());
await installFromNpm(name);
} catch (error) {
logger.error("[PH - Install Extension]: failed to install from NPM", error);

View File

@ -45,12 +45,15 @@ export async function bootstrap(App: AppComponent) {
lensProtocolRouter.init();
lensProtocolRouter.onMissingExtension(async name => {
if (!extensionLoader.isInstalled(name)) {
logger.info(`[PROTOCOL ROUTER] Extension ${name} not installed, installing..`);
logger.info(`[PROTOCOL ROUTER]: Extension ${name} not installed, installing..`);
// TODO: This actually resolves before the extension installation is complete
return installFromNpm(name);
await installFromNpm(name);
return true;
} else {
logger.info(`[PROTOCOL ROUTER] Extension already installed, but route is missing.`);
logger.info(`[PROTOCOL ROUTER]: Extension already installed, but route is missing.`);
return false;
}
});
protocolEndpoints.registerHandlers();

View File

@ -165,7 +165,7 @@ async function requestInstall(init: InstallRequest | InstallRequest[]) {
if (!folderExists) {
// auto-install extension if not yet exists
unpackExtension(install);
return unpackExtension(install);
} else {
// If we show the confirmation dialog, we stop the install spinner until user clicks ok
// and the install continues
@ -210,7 +210,11 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version }
try {
// extract to temp folder first
await fse.remove(unpackingTempFolder).catch(Function);
try {
await fse.remove(unpackingTempFolder);
} catch (err) {
// ignore error
}
await fse.ensureDir(unpackingTempFolder);
await extractTar(tempFile, { cwd: unpackingTempFolder });
@ -237,8 +241,12 @@ async function unpackExtension({ fileName, tempFile, manifest: { name, version }
}
} finally {
// clean up
fse.remove(unpackingTempFolder).catch(Function);
fse.unlink(tempFile).catch(Function);
try {
await fse.remove(unpackingTempFolder);
await fse.unlink(tempFile);
} catch (err) {
// ignore error
}
}
}
@ -352,6 +360,8 @@ async function installFromSelectFileDialog() {
export async function installFromNpm(packageName: string) {
const tarballUrl = await extensionLoader.getNpmPackageTarballUrl(packageName);
Notifications.info(`Installing ${packageName}`);
return installFromUrlOrPath(tarballUrl);
}

View File

@ -108,25 +108,28 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
* @param params request parameters described in IPodLogsQuery interface
* @returns {Promise} A fetch request promise
*/
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>) => {
loadLogs = async (tabId: TabId, params: Partial<IPodLogsQuery>): Promise<string[]> => {
const data = this.getData(tabId);
const { selectedContainer, previous } = data;
const pod = new Pod(data.pod);
const namespace = pod.getNs();
const name = pod.getName();
return podsApi.getLogs({ namespace, name }, {
...params,
timestamps: true, // Always setting timestampt to separate old logs from new ones
container: selectedContainer.name,
previous
}).then(result => {
const logs = [...result.split("\n")]; // Transform them into array
try {
const result = await podsApi.getLogs({ namespace, name }, {
...params,
timestamps: true, // Always setting timestampt to separate old logs from new ones
container: selectedContainer.name,
previous
});
const logs = result.split(/(\r?\n)+/g);
logs.pop(); // Remove last empty element
logs.pop();// Remove last empty element
return logs;
});
} catch (err) {
// ignore error
}
};
/**
@ -145,7 +148,7 @@ export class PodLogsStore extends DockTabStore<IPodLogsData> {
* @returns {number} Length of log lines
*/
@computed
get lines() {
get lines(): number {
const id = dockStore.selectedTabId;
const logs = this.logs.get(id);