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:
parent
e79e8eb76c
commit
0a6bafc1be
@ -3,7 +3,7 @@
|
||||
"productName": "OpenLens",
|
||||
"description": "OpenLens - Open Source IDE for Kubernetes",
|
||||
"homepage": "https://github.com/lensapp/lens",
|
||||
"version": "5.3.2",
|
||||
"version": "5.3.3",
|
||||
"main": "static/build/main.js",
|
||||
"copyright": "© 2021 OpenLens Authors",
|
||||
"license": "MIT",
|
||||
@ -131,6 +131,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"rpm": {
|
||||
"fpm": [
|
||||
"--rpm-rpmbuild-define=%define _build_id_links none"
|
||||
]
|
||||
},
|
||||
"mac": {
|
||||
"hardenedRuntime": true,
|
||||
"gatekeeperAssess": false,
|
||||
@ -380,7 +385,6 @@
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-node-externals": "^1.7.2",
|
||||
"what-input": "^5.2.10",
|
||||
"xterm": "^4.14.1",
|
||||
"xterm-addon-fit": "^0.5.0"
|
||||
}
|
||||
|
||||
@ -78,7 +78,17 @@ export class AppPaths {
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
@ -26,6 +26,12 @@ import { KubeObject } from "../kube-object";
|
||||
import AbortController from "abort-controller";
|
||||
import { delay } from "../../utils/delay";
|
||||
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 {
|
||||
static kind = "Pod";
|
||||
@ -33,7 +39,11 @@ class TestKubeObject extends KubeObject {
|
||||
static apiBase = "/api/v1/pods";
|
||||
}
|
||||
|
||||
class TestKubeApi extends KubeApi<TestKubeObject> { }
|
||||
class TestKubeApi extends KubeApi<TestKubeObject> {
|
||||
public async checkPreferredVersion() {
|
||||
return super.checkPreferredVersion();
|
||||
}
|
||||
}
|
||||
|
||||
describe("forRemoteCluster", () => {
|
||||
it("builds api client for KubeObject", async () => {
|
||||
@ -184,6 +194,94 @@ describe("KubeApi", () => {
|
||||
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", () => {
|
||||
let api: TestKubeApi;
|
||||
|
||||
|
||||
@ -37,7 +37,7 @@ export interface IKubeApiLinkRef {
|
||||
apiPrefix?: string;
|
||||
apiVersion: string;
|
||||
resource: string;
|
||||
name: string;
|
||||
name?: string;
|
||||
namespace?: string;
|
||||
}
|
||||
|
||||
@ -145,15 +145,18 @@ function _parseKubeApi(path: string): IKubeApiParsed {
|
||||
};
|
||||
}
|
||||
|
||||
export function createKubeApiURL(ref: IKubeApiLinkRef): string {
|
||||
const { apiPrefix = "/apis", resource, apiVersion, name } = ref;
|
||||
let { namespace } = ref;
|
||||
export function createKubeApiURL({ apiPrefix = "/apis", resource, apiVersion, name, namespace }: IKubeApiLinkRef): string {
|
||||
const parts = [apiPrefix, apiVersion];
|
||||
|
||||
if (namespace) {
|
||||
namespace = `namespaces/${namespace}`;
|
||||
parts.push("namespaces", namespace);
|
||||
}
|
||||
|
||||
return [apiPrefix, apiVersion, namespace, resource, name]
|
||||
.filter(v => v)
|
||||
.join("/");
|
||||
parts.push(resource);
|
||||
|
||||
if (name) {
|
||||
parts.push(name);
|
||||
}
|
||||
|
||||
return parts.join("/");
|
||||
}
|
||||
|
||||
@ -38,9 +38,14 @@ import AbortController from "abort-controller";
|
||||
import { Agent, AgentOptions } from "https";
|
||||
import type { Patch } from "rfc6902";
|
||||
|
||||
/**
|
||||
* The options used for creating a `KubeApi`
|
||||
*/
|
||||
export interface IKubeApiOptions<T extends KubeObject> {
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@ -52,11 +57,33 @@ export interface IKubeApiOptions<T extends KubeObject> {
|
||||
*/
|
||||
fallbackApiBases?: string[];
|
||||
|
||||
objectConstructor: KubeObjectConstructor<T>;
|
||||
request?: KubeJsonApi;
|
||||
isNamespaced?: boolean;
|
||||
kind?: string;
|
||||
/**
|
||||
* If `true` then will check all declared apiBases against the kube api server
|
||||
* for the first accepted one.
|
||||
*/
|
||||
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 {
|
||||
@ -249,11 +276,11 @@ export interface DeleteResourceDescriptor extends ResourceDescriptor {
|
||||
|
||||
export class KubeApi<T extends KubeObject> {
|
||||
readonly kind: string;
|
||||
readonly apiBase: string;
|
||||
readonly apiPrefix: string;
|
||||
readonly apiGroup: string;
|
||||
readonly apiVersion: string;
|
||||
readonly apiVersionPreferred?: string;
|
||||
apiBase: string;
|
||||
apiPrefix: string;
|
||||
apiGroup: string;
|
||||
apiVersionPreferred?: string;
|
||||
readonly apiResource: string;
|
||||
readonly isNamespaced: boolean;
|
||||
|
||||
@ -264,23 +291,18 @@ export class KubeApi<T extends KubeObject> {
|
||||
private watchId = 1;
|
||||
|
||||
constructor(protected options: IKubeApiOptions<T>) {
|
||||
const {
|
||||
objectConstructor,
|
||||
request = apiKube,
|
||||
kind = options.objectConstructor?.kind,
|
||||
isNamespaced = options.objectConstructor?.namespaced,
|
||||
} = options || {};
|
||||
|
||||
const { objectConstructor, request, kind, isNamespaced } = options;
|
||||
const { apiBase, apiPrefix, apiGroup, apiVersion, resource } = parseKubeApi(options.apiBase || objectConstructor.apiBase);
|
||||
|
||||
this.kind = kind;
|
||||
this.isNamespaced = isNamespaced;
|
||||
this.options = options;
|
||||
this.kind = kind ?? objectConstructor.kind;
|
||||
this.isNamespaced = isNamespaced ?? objectConstructor.namespaced ?? false;
|
||||
this.apiBase = apiBase;
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = apiGroup;
|
||||
this.apiVersion = apiVersion;
|
||||
this.apiResource = resource;
|
||||
this.request = request;
|
||||
this.request = request ?? apiKube;
|
||||
this.objectConstructor = objectConstructor;
|
||||
|
||||
this.parseResponse = this.parseResponse.bind(this);
|
||||
@ -353,21 +375,16 @@ export class KubeApi<T extends KubeObject> {
|
||||
const { apiPrefix, apiGroup } = await this.getPreferredVersionPrefixGroup();
|
||||
|
||||
// The apiPrefix and apiGroup might change due to fallbackApiBases, so we must override them
|
||||
Object.defineProperty(this, "apiPrefix", {
|
||||
value: apiPrefix,
|
||||
});
|
||||
Object.defineProperty(this, "apiGroup", {
|
||||
value: apiGroup,
|
||||
});
|
||||
this.apiPrefix = apiPrefix;
|
||||
this.apiGroup = 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", {
|
||||
value: res?.preferredVersion?.version ?? null,
|
||||
});
|
||||
this.apiVersionPreferred = res?.preferredVersion?.version ?? null;
|
||||
|
||||
if (this.apiVersionPreferred) {
|
||||
Object.defineProperty(this, "apiBase", { value: this.getUrl() });
|
||||
this.apiBase = this.computeApiBase();
|
||||
apiManager.registerApi(this.apiBase, this);
|
||||
}
|
||||
}
|
||||
@ -385,7 +402,15 @@ export class KubeApi<T extends KubeObject> {
|
||||
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({
|
||||
apiPrefix: this.apiPrefix,
|
||||
apiVersion: this.apiVersionWithGroup,
|
||||
|
||||
@ -90,15 +90,23 @@ if (process.env.LENS_DISABLE_GPU) {
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
logger.debug("[APP-MAIN] initializing remote");
|
||||
initializeRemote();
|
||||
|
||||
logger.debug("[APP-MAIN] configuring packages");
|
||||
configurePackages();
|
||||
|
||||
mangleProxyEnv();
|
||||
|
||||
logger.debug("[APP-MAIN] initializing ipc main handlers");
|
||||
initializers.initIpcMainHandlers();
|
||||
|
||||
if (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()) {
|
||||
app.exit();
|
||||
} else {
|
||||
@ -112,6 +120,8 @@ if (!app.requestSingleInstanceLock()) {
|
||||
}
|
||||
|
||||
app.on("second-instance", (event, argv) => {
|
||||
logger.debug("second-instance message");
|
||||
|
||||
const lprm = LensProtocolRouterMain.createInstance();
|
||||
|
||||
for (const arg of argv) {
|
||||
@ -295,6 +305,8 @@ autoUpdater.on("before-quit-for-update", () => {
|
||||
});
|
||||
|
||||
app.on("will-quit", (event) => {
|
||||
logger.debug("will-quit message");
|
||||
|
||||
// This is called when the close button of the main window is clicked
|
||||
|
||||
const lprm = LensProtocolRouterMain.getInstance(false);
|
||||
@ -324,6 +336,8 @@ app.on("will-quit", (event) => {
|
||||
});
|
||||
|
||||
app.on("open-url", (event, rawUrl) => {
|
||||
logger.debug("open-url message");
|
||||
|
||||
// lens:// protocol handler
|
||||
event.preventDefault();
|
||||
LensProtocolRouterMain.getInstance().route(rawUrl);
|
||||
@ -343,3 +357,5 @@ export {
|
||||
Mobx,
|
||||
LensExtensions,
|
||||
};
|
||||
|
||||
logger.debug("[APP-MAIN] waiting for 'ready' and other messages");
|
||||
|
||||
@ -158,6 +158,8 @@ export abstract class ShellSession {
|
||||
cwd,
|
||||
env,
|
||||
name: "xterm-256color",
|
||||
// TODO: Something else is broken here so we need to force the use of winPty on windows
|
||||
useConpty: false,
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -38,7 +38,6 @@ import { ClusterPageRegistry, getExtensionPageUrl } from "../extensions/registri
|
||||
import { ExtensionLoader } from "../extensions/extension-loader";
|
||||
import { appEventBus } from "../common/event-bus";
|
||||
import { requestMain } from "../common/ipc";
|
||||
import whatInput from "what-input";
|
||||
import { clusterSetFrameIdHandler } from "../common/cluster-ipc";
|
||||
import { ClusterPageMenuRegistration, ClusterPageMenuRegistry } from "../extensions/registries";
|
||||
import { StatefulSetScaleDialog } from "./components/+workloads-statefulsets/statefulset-scale-dialog";
|
||||
@ -122,8 +121,6 @@ export class ClusterFrame extends React.Component {
|
||||
unmountComponentAtNode(rootElem);
|
||||
};
|
||||
|
||||
whatInput.ask(); // Start to monitor user input device
|
||||
|
||||
const clusterContext = new FrameContext(cluster);
|
||||
|
||||
// Setup hosted cluster context
|
||||
|
||||
@ -57,9 +57,7 @@ export class CrdResources extends React.Component<Props> {
|
||||
}
|
||||
|
||||
@computed get store() {
|
||||
if (!this.crd) return null;
|
||||
|
||||
return apiManager.getStore(this.crd.getResourceApiBase());
|
||||
return apiManager.getStore(this.crd?.getResourceApiBase());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@ -29,15 +29,14 @@ import { CRDResourceStore } from "./crd-resource.store";
|
||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||
|
||||
function initStore(crd: CustomResourceDefinition) {
|
||||
const apiBase = crd.getResourceApiBase();
|
||||
const kind = crd.getResourceKind();
|
||||
const isNamespaced = crd.isNamespaced();
|
||||
const api = apiManager.getApi(apiBase) ?? new KubeApi({
|
||||
objectConstructor: KubeObject,
|
||||
apiBase,
|
||||
kind,
|
||||
isNamespaced,
|
||||
});
|
||||
const objectConstructor = class extends KubeObject {
|
||||
static readonly kind = crd.getResourceKind();
|
||||
static readonly namespaced = crd.isNamespaced();
|
||||
static readonly apiBase = crd.getResourceApiBase();
|
||||
};
|
||||
|
||||
const api = apiManager.getApi(objectConstructor.apiBase)
|
||||
?? new KubeApi({ objectConstructor });
|
||||
|
||||
if (!apiManager.getStore(api)) {
|
||||
apiManager.registerStore(new CRDResourceStore(api));
|
||||
|
||||
@ -153,7 +153,7 @@ export class ClusterStatus extends React.Component<Props> {
|
||||
return (
|
||||
<div className={cssNames(styles.status, "flex column box center align-center justify-center", this.props.className)}>
|
||||
<div className="flex items-center column gaps">
|
||||
<h2>{this.entity.getName()}</h2>
|
||||
<h2>{this.entity?.getName() ?? this.cluster.name}</h2>
|
||||
{this.renderStatusIcon()}
|
||||
{this.renderAuthenticationOutput()}
|
||||
{this.renderReconnectionHelp()}
|
||||
|
||||
@ -127,7 +127,7 @@
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--color-active);
|
||||
color: var(--textColorAccent);
|
||||
box-shadow: 0 0 0 2px var(--iconActiveBackground);
|
||||
background-color: var(--iconActiveBackground);
|
||||
}
|
||||
@ -137,16 +137,8 @@
|
||||
transition: 250ms color, 250ms opacity, 150ms background-color, 150ms box-shadow;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&.focusable:focus:not(:hover) {
|
||||
&.focusable:focus-visible {
|
||||
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 {
|
||||
|
||||
@ -32,7 +32,6 @@
|
||||
&:focus-visible {
|
||||
.dropdown {
|
||||
box-shadow: 0 0 0 2px var(--focus-color);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -61,11 +61,11 @@ export function initCatalogCategoryRegistryEntries() {
|
||||
ctx.menuItems.push(
|
||||
{
|
||||
icon: "create_new_folder",
|
||||
title: "Sync kubeconfig folders(s)",
|
||||
title: "Sync kubeconfig folder(s)",
|
||||
defaultAction: true,
|
||||
onClick: async () => {
|
||||
await PathPicker.pick({
|
||||
label: "Sync folders(s)",
|
||||
label: "Sync folder(s)",
|
||||
buttonLabel: "Sync",
|
||||
properties: ["showHiddenFiles", "multiSelections", "openDirectory"],
|
||||
onPick: addSyncEntries,
|
||||
|
||||
@ -14370,11 +14370,6 @@ websocket-extensions@>=0.1.1:
|
||||
resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
|
||||
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:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user