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",
"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"
}

View File

@ -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()));
}

View File

@ -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;

View File

@ -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("/");
}

View File

@ -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,

View File

@ -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");

View File

@ -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,
}));
}

View File

@ -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

View File

@ -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() {

View File

@ -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));

View File

@ -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()}

View File

@ -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 {

View File

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

View File

@ -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,

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"
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"