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

Release/v5.3.3 (#4548)

Co-authored-by: Alex Andreev <alex.andreev.email@gmail.com>
Co-authored-by: Jim Ehrismann <40840436+jim-docker@users.noreply.github.com>
Co-authored-by: Mario Sarcher <mario@sarcher.de>
This commit is contained in:
Sebastian Malton 2021-12-14 15:44:04 -05:00 committed by GitHub
parent e79e8eb76c
commit 0a6bafc1be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 214 additions and 76 deletions

View File

@ -3,7 +3,7 @@
"productName": "OpenLens", "productName": "OpenLens",
"description": "OpenLens - Open Source IDE for Kubernetes", "description": "OpenLens - Open Source IDE for Kubernetes",
"homepage": "https://github.com/lensapp/lens", "homepage": "https://github.com/lensapp/lens",
"version": "5.3.2", "version": "5.3.3",
"main": "static/build/main.js", "main": "static/build/main.js",
"copyright": "© 2021 OpenLens Authors", "copyright": "© 2021 OpenLens Authors",
"license": "MIT", "license": "MIT",
@ -131,6 +131,11 @@
} }
] ]
}, },
"rpm": {
"fpm": [
"--rpm-rpmbuild-define=%define _build_id_links none"
]
},
"mac": { "mac": {
"hardenedRuntime": true, "hardenedRuntime": true,
"gatekeeperAssess": false, "gatekeeperAssess": false,
@ -380,7 +385,6 @@
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2", "webpack-dev-server": "^3.11.2",
"webpack-node-externals": "^1.7.2", "webpack-node-externals": "^1.7.2",
"what-input": "^5.2.10",
"xterm": "^4.14.1", "xterm": "^4.14.1",
"xterm-addon-fit": "^0.5.0" "xterm-addon-fit": "^0.5.0"
} }

View File

@ -78,7 +78,17 @@ export class AppPaths {
app.setPath("userData", path.join(app.getPath("appData"), app.getName())); app.setPath("userData", path.join(app.getPath("appData"), app.getName()));
AppPaths.paths.set(fromEntries(pathNames.map(pathName => [pathName, app.getPath(pathName)]))); const getPath = (pathName: PathName) => {
try {
return app.getPath(pathName);
} catch {
logger.debug(`[APP-PATHS] No path found for ${pathName}`);
return "";
}
};
AppPaths.paths.set(fromEntries(pathNames.map(pathName => [pathName, getPath(pathName)] as const).filter(([, path]) => path)));
ipcMain.handle(AppPaths.ipcChannel, () => toJS(AppPaths.paths.get())); ipcMain.handle(AppPaths.ipcChannel, () => toJS(AppPaths.paths.get()));
} }

View File

@ -26,6 +26,12 @@ import { KubeObject } from "../kube-object";
import AbortController from "abort-controller"; import AbortController from "abort-controller";
import { delay } from "../../utils/delay"; import { delay } from "../../utils/delay";
import { PassThrough } from "stream"; import { PassThrough } from "stream";
import { ApiManager, apiManager } from "../api-manager";
import { Ingress, Pod } from "../endpoints";
jest.mock("../api-manager");
const mockApiManager = apiManager as jest.Mocked<ApiManager>;
class TestKubeObject extends KubeObject { class TestKubeObject extends KubeObject {
static kind = "Pod"; static kind = "Pod";
@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject {
static apiBase = "/api/v1/pods"; static apiBase = "/api/v1/pods";
} }
class TestKubeApi extends KubeApi<TestKubeObject> { } class TestKubeApi extends KubeApi<TestKubeObject> {
public async checkPreferredVersion() {
return super.checkPreferredVersion();
}
}
describe("forRemoteCluster", () => { describe("forRemoteCluster", () => {
it("builds api client for KubeObject", async () => { it("builds api client for KubeObject", async () => {
@ -184,6 +194,94 @@ describe("KubeApi", () => {
expect(kubeApi.apiGroup).toEqual("extensions"); expect(kubeApi.apiGroup).toEqual("extensions");
}); });
describe("checkPreferredVersion", () => {
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
objectConstructor: Ingress,
checkPreferredVersion: true,
fallbackApiBases: ["/apis/extensions/v1beta1/ingresses"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/networking.k8s.io/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions/v1beta1");
return {
resources: [
{
name: "ingresses",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/apis/extensions");
return {
preferredVersion: {
version: "v1beta1",
},
};
}),
} as any,
});
await api.checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/apis/extensions/v1beta1/ingresses", expect.anything());
});
it("registers with apiManager if checkPreferredVersion changes apiVersionPreferred with non-grouped apis", async () => {
expect.hasAssertions();
const api = new TestKubeApi({
objectConstructor: Pod,
checkPreferredVersion: true,
fallbackApiBases: ["/api/v1beta1/pods"],
request: {
get: jest.fn()
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1");
throw new Error("no");
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api/v1beta1");
return {
resources: [
{
name: "pods",
},
],
};
})
.mockImplementationOnce((path: string) => {
expect(path).toBe("/api");
return {
preferredVersion: {
version: "v1beta1",
},
};
}),
} as any,
});
await api.checkPreferredVersion();
expect(api.apiVersionPreferred).toBe("v1beta1");
expect(mockApiManager.registerApi).toBeCalledWith("/api/v1beta1/pods", expect.anything());
});
});
describe("patch", () => { describe("patch", () => {
let api: TestKubeApi; let api: TestKubeApi;

View File

@ -37,7 +37,7 @@ export interface IKubeApiLinkRef {
apiPrefix?: string; apiPrefix?: string;
apiVersion: string; apiVersion: string;
resource: string; resource: string;
name: string; name?: string;
namespace?: string; namespace?: string;
} }
@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed {
}; };
} }
export function createKubeApiURL(ref: IKubeApiLinkRef): string { export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
const { apiPrefix = "/apis", resource, apiVersion, name } = ref; const parts = [apiPrefix, apiVersion];
let { namespace } = ref;
if (namespace) { if (namespace) {
namespace = `namespaces/${namespace}`; parts.push("namespaces", namespace);
} }
return [apiPrefix, apiVersion, namespace, resource, name] parts.push(resource);
.filter(v => v)
.join("/"); if (name) {
parts.push(name);
}
return parts.join("/");
} }

View File

@ -38,9 +38,14 @@ import AbortController from "abort-controller";
import { Agent, AgentOptions } from "https"; import { Agent, AgentOptions } from "https";
import type { Patch } from "rfc6902"; import type { Patch } from "rfc6902";
/**
* The options used for creating a `KubeApi`
*/
export interface IKubeApiOptions<T extends KubeObject> { export interface IKubeApiOptions<T extends KubeObject> {
/** /**
* base api-path for listing all resources, e.g. "/api/v1/pods" * base api-path for listing all resources, e.g. "/api/v1/pods"
*
* If not specified then will be the one on the `objectConstructor`
*/ */
apiBase?: string; apiBase?: string;
@ -52,11 +57,33 @@ export interface IKubeApiOptions<T extends KubeObject> {
*/ */
fallbackApiBases?: string[]; fallbackApiBases?: string[];
objectConstructor: KubeObjectConstructor<T>; /**
request?: KubeJsonApi; * If `true` then will check all declared apiBases against the kube api server
isNamespaced?: boolean; * for the first accepted one.
kind?: string; */
checkPreferredVersion?: boolean; checkPreferredVersion?: boolean;
/**
* The constructor for the kube objects returned from the API
*/
objectConstructor: KubeObjectConstructor<T>;
/**
* The api instance to use for making requests
*
* @default apiKube
*/
request?: KubeJsonApi;
/**
* @deprecated should be specified by `objectConstructor`
*/
isNamespaced?: boolean;
/**
* @deprecated should be specified by `objectConstructor`
*/
kind?: string;
} }
export interface IKubeApiQueryParams { export interface IKubeApiQueryParams {
@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor {
export class KubeApi<T extends KubeObject> { export class KubeApi<T extends KubeObject> {
readonly kind: string; readonly kind: string;
readonly apiBase: string;
readonly apiPrefix: string;
readonly apiGroup: string;
readonly apiVersion: string; readonly apiVersion: string;
readonly apiVersionPreferred?: string; apiBase: string;
apiPrefix: string;
apiGroup: string;
apiVersionPreferred?: string;
readonly apiResource: string; readonly apiResource: string;
readonly isNamespaced: boolean; readonly isNamespaced: boolean;
@ -264,23 +291,18 @@ export class KubeApi<T extends KubeObject> {
private watchId = 1; private watchId = 1;
constructor(protected options: IKubeApiOptions<T>) { constructor(protected options: IKubeApiOptions<T>) {
const { const { objectConstructor, request, kind, isNamespaced } = options;
objectConstructor,
request = apiKube,
kind = options.objectConstructor?.kind,
isNamespaced = options.objectConstructor?.namespaced,
} = options || {};
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase); const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
this.kind = kind; this.options = options;
this.isNamespaced = isNamespaced; this.kind = kind ?? objectConstructor.kind;
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
this.apiBase = apiBase; this.apiBase = apiBase;
this.apiPrefix = apiPrefix; this.apiPrefix = apiPrefix;
this.apiGroup = apiGroup; this.apiGroup = apiGroup;
this.apiVersion = apiVersion; this.apiVersion = apiVersion;
this.apiResource = resource; this.apiResource = resource;
this.request = request; this.request = request ?? apiKube;
this.objectConstructor = objectConstructor; this.objectConstructor = objectConstructor;
this.parseResponse = this.parseResponse.bind(this); this.parseResponse = this.parseResponse.bind(this);
@ -353,21 +375,16 @@ export class KubeApi<T extends KubeObject> {
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup(); const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them // The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
Object.defineProperty(this, "apiPrefix", { this.apiPrefix = apiPrefix;
value: apiPrefix, this.apiGroup = apiGroup;
});
Object.defineProperty(this, "apiGroup", {
value: apiGroup,
});
const res = await this.request.get<IKubePreferredVersion>(`${this.apiPrefix}/${this.apiGroup}`); const url = [apiPrefix, apiGroup].filter(Boolean).join("/");
const res = await this.request.get<IKubePreferredVersion>(url);
Object.defineProperty(this, "apiVersionPreferred", { this.apiVersionPreferred = res?.preferredVersion?.version ?? null;
value: res?.preferredVersion?.version ?? null,
});
if (this.apiVersionPreferred) { if (this.apiVersionPreferred) {
Object.defineProperty(this, "apiBase", { value: this.getUrl() }); this.apiBase = this.computeApiBase();
apiManager.registerApi(this.apiBase, this); apiManager.registerApi(this.apiBase, this);
} }
} }
@ -385,7 +402,15 @@ export class KubeApi<T extends KubeObject> {
return this.list(params, { limit: 1 }); return this.list(params, { limit: 1 });
} }
getUrl({ name, namespace = "default" }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) { private computeApiBase(): string {
return createKubeApiURL({
apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup,
resource: this.apiResource,
});
}
getUrl({ name, namespace }: Partial<ResourceDescriptor> = {}, query?: Partial<IKubeApiQueryParams>) {
const resourcePath = createKubeApiURL({ const resourcePath = createKubeApiURL({
apiPrefix: this.apiPrefix, apiPrefix: this.apiPrefix,
apiVersion: this.apiVersionWithGroup, apiVersion: this.apiVersionWithGroup,

View File

@ -90,15 +90,23 @@ if (process.env.LENS_DISABLE_GPU) {
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
} }
logger.debug("[APP-MAIN] initializing remote");
initializeRemote(); initializeRemote();
logger.debug("[APP-MAIN] configuring packages");
configurePackages(); configurePackages();
mangleProxyEnv(); mangleProxyEnv();
logger.debug("[APP-MAIN] initializing ipc main handlers");
initializers.initIpcMainHandlers(); initializers.initIpcMainHandlers();
if (app.commandLine.getSwitchValue("proxy-server") !== "") { if (app.commandLine.getSwitchValue("proxy-server") !== "") {
process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server"); process.env.HTTPS_PROXY = app.commandLine.getSwitchValue("proxy-server");
} }
logger.debug("[APP-MAIN] Lens protocol routing main");
if (!app.requestSingleInstanceLock()) { if (!app.requestSingleInstanceLock()) {
app.exit(); app.exit();
} else { } else {
@ -112,6 +120,8 @@ if (!app.requestSingleInstanceLock()) {
} }
app.on("second-instance", (event, argv) => { app.on("second-instance", (event, argv) => {
logger.debug("second-instance message");
const lprm = LensProtocolRouterMain.createInstance(); const lprm = LensProtocolRouterMain.createInstance();
for (const arg of argv) { for (const arg of argv) {
@ -295,6 +305,8 @@ autoUpdater.on("before-quit-for-update", () => {
}); });
app.on("will-quit", (event) => { app.on("will-quit", (event) => {
logger.debug("will-quit message");
// This is called when the close button of the main window is clicked // This is called when the close button of the main window is clicked
const lprm = LensProtocolRouterMain.getInstance(false); const lprm = LensProtocolRouterMain.getInstance(false);
@ -324,6 +336,8 @@ app.on("will-quit", (event) => {
}); });
app.on("open-url", (event, rawUrl) => { app.on("open-url", (event, rawUrl) => {
logger.debug("open-url message");
// lens:// protocol handler // lens:// protocol handler
event.preventDefault(); event.preventDefault();
LensProtocolRouterMain.getInstance().route(rawUrl); LensProtocolRouterMain.getInstance().route(rawUrl);
@ -343,3 +357,5 @@ export {
Mobx, Mobx,
LensExtensions, LensExtensions,
}; };
logger.debug("[APP-MAIN] waiting for 'ready' and other messages");

View File

@ -158,6 +158,8 @@ export abstract class ShellSession {
cwd, cwd,
env, env,
name: "xterm-256color", name: "xterm-256color",
// TODO: Something else is broken here so we need to force the use of winPty on windows
useConpty: false,
})); }));
} }

View File

@ -38,7 +38,6 @@ import { ClusterPageRegistry, getExtensionPageUrl } from "../extensions/registri
import { ExtensionLoader } from "../extensions/extension-loader"; import { ExtensionLoader } from "../extensions/extension-loader";
import { appEventBus } from "../common/event-bus"; import { appEventBus } from "../common/event-bus";
import { requestMain } from "../common/ipc"; import { requestMain } from "../common/ipc";
import whatInput from "what-input";
import { clusterSetFrameIdHandler } from "../common/cluster-ipc"; import { clusterSetFrameIdHandler } from "../common/cluster-ipc";
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries"; import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries";
import { StatefulSetScaleDialog } from "./components/+workloads-statefulsets/statefulset-scale-dialog"; import { StatefulSetScaleDialog } from "./components/+workloads-statefulsets/statefulset-scale-dialog";
@ -122,8 +121,6 @@ export class ClusterFrame extends React.Component {
unmountComponentAtNode(rootElem); unmountComponentAtNode(rootElem);
}; };
whatInput.ask(); // Start to monitor user input device
const clusterContext = new FrameContext(cluster); const clusterContext = new FrameContext(cluster);
// Setup hosted cluster context // Setup hosted cluster context

View File

@ -57,9 +57,7 @@ export class CrdResources extends React.Component<Props> {
} }
@computed get store() { @computed get store() {
if (!this.crd) return null; return apiManager.getStore(this.crd?.getResourceApiBase());
return apiManager.getStore(this.crd.getResourceApiBase());
} }
render() { render() {

View File

@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store";
import { KubeObject } from "../../../common/k8s-api/kube-object"; import { KubeObject } from "../../../common/k8s-api/kube-object";
function initStore(crd: CustomResourceDefinition) { function initStore(crd: CustomResourceDefinition) {
const apiBase = crd.getResourceApiBase(); const objectConstructor = class extends KubeObject {
const kind = crd.getResourceKind(); static readonly kind = crd.getResourceKind();
const isNamespaced = crd.isNamespaced(); static readonly namespaced = crd.isNamespaced();
const api = apiManager.getApi(apiBase) ?? new KubeApi({ static readonly apiBase = crd.getResourceApiBase();
objectConstructor: KubeObject, };
apiBase,
kind, const api = apiManager.getApi(objectConstructor.apiBase)
isNamespaced, ?? new KubeApi({ objectConstructor });
});
if (!apiManager.getStore(api)) { if (!apiManager.getStore(api)) {
apiManager.registerStore(new CRDResourceStore(api)); apiManager.registerStore(new CRDResourceStore(api));

View File

@ -153,7 +153,7 @@ export class ClusterStatus extends React.Component<Props> {
return ( return (
<div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}> <div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}>
<div className="flex items-center column gaps"> <div className="flex items-center column gaps">
<h2>{this.entity.getName()}</h2> <h2>{this.entity?.getName() ?? this.cluster.name}</h2>
{this.renderStatusIcon()} {this.renderStatusIcon()}
{this.renderAuthenticationOutput()} {this.renderAuthenticationOutput()}
{this.renderReconnectionHelp()} {this.renderReconnectionHelp()}

View File

@ -127,7 +127,7 @@
} }
&.active { &.active {
color: var(--color-active); color: var(--textColorAccent);
box-shadow: 0 0 0 2px var(--iconActiveBackground); box-shadow: 0 0 0 2px var(--iconActiveBackground);
background-color: var(--iconActiveBackground); background-color: var(--iconActiveBackground);
} }
@ -137,16 +137,8 @@
transition: 250ms color, 250ms opacity, 150ms background-color, 150ms box-shadow; transition: 250ms color, 250ms opacity, 150ms background-color, 150ms box-shadow;
border-radius: var(--border-radius); border-radius: var(--border-radius);
&.focusable:focus:not(:hover) { &.focusable:focus-visible {
box-shadow: 0 0 0 2px var(--focus-color); box-shadow: 0 0 0 2px var(--focus-color);
[data-whatintent='mouse'] & {
box-shadow: none;
&.active {
box-shadow: 0 0 0 2px var(--iconActiveBackground);
}
}
} }
&:hover { &:hover {

View File

@ -32,7 +32,6 @@
&:focus-visible { &:focus-visible {
.dropdown { .dropdown {
box-shadow: 0 0 0 2px var(--focus-color); box-shadow: 0 0 0 2px var(--focus-color);
color: white;
} }
} }

View File

@ -61,11 +61,11 @@ export function initCatalogCategoryRegistryEntries() {
ctx.menuItems.push( ctx.menuItems.push(
{ {
icon: "create_new_folder", icon: "create_new_folder",
title: "Sync kubeconfig folders(s)", title: "Sync kubeconfig folder(s)",
defaultAction: true, defaultAction: true,
onClick: async () => { onClick: async () => {
await PathPicker.pick({ await PathPicker.pick({
label: "Sync folders(s)", label: "Sync folder(s)",
buttonLabel: "Sync", buttonLabel: "Sync",
properties: ["showHiddenFiles", "multiSelections", "openDirectory"], properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
onPick: addSyncEntries, onPick: addSyncEntries,

View File

@ -14370,11 +14370,6 @@ websocket-extensions@>=0.1.1:
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
what-input@^5.2.10:
version "5.2.10"
resolved "https://registry.yarnpkg.com/what-input/-/what-input-5.2.10.tgz#f79f5b65cf95d75e55e6d580bb0a6b98174cad4e"
integrity sha512-7AQoIMGq7uU8esmKniOtZG3A+pzlwgeyFpkS3f/yzRbxknSL68tvn5gjE6bZ4OMFxCPjpaBd2udUTqlZ0HwrXQ==
whatwg-encoding@^1.0.5: whatwg-encoding@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"