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

address review comments

Signed-off-by: Jim Ehrismann <jehrismann@mirantis.com>
This commit is contained in:
Jim Ehrismann 2021-06-16 14:54:50 -04:00
parent da8017a0ee
commit ee76d81d15
11 changed files with 121 additions and 245 deletions

View File

@ -37,7 +37,7 @@ Keep up with Lens and the Lens Extension API by reviewing the [release notes](ht
## Important changes since Lens v4 ## Important changes since Lens v4
Lens has undergone major design changes in v5 which have resulted in a new extension API. Lens has undergone major design improvements in v5, which have resulted in several large changes to the extension API.
Workspaces are gone, and the catalog is introduced for containing clusters, as well as other items, including custom entities. Workspaces are gone, and the catalog is introduced for containing clusters, as well as other items, including custom entities.
Lens has migrated from using mobx 5 to mobx 6 for internal state management, and this may have ramifications for extension implementations. Lens has migrated from using mobx 5 to mobx 6 for internal state management, and this may have ramifications for extension implementations.
Although the API retains many components from v4, given these changes, extensions written for Lens v4 are not compatible with the Lens v5 extension API. Although the API retains many components from v4, given these changes, extensions written for Lens v4 are not compatible with the Lens v5 extension API.

View File

@ -7,15 +7,17 @@ The `package.json` for your extension must have an `"engines"` field specifying
"lens": "^5.0.0-beta.7" "lens": "^5.0.0-beta.7"
}, },
``` ```
Note that Lens v5 supports all the range semantics that [semver](https://www.npmjs.com/package/semver) provides.
* Types and components have been reorganized, many have been grouped by process (`Main` and `Renderer`) plus those not specific to a process (`Common`). * Types and components have been reorganized, many have been grouped by process (`Main` and `Renderer`) plus those not specific to a process (`Common`).
For example the `LensMainExtension` class is now referred to by `Main.LensExtension`. For example the `LensMainExtension` class is now referred to by `Main.LensExtension`.
See the [API Refernce](extensions/api/README.md) for the new organization. See the [API Reference](api/README.md) for the new organization.
* The `globalPageMenus` field of the Renderer extension class (now `Renderer.LensExtension`) is removed. * The `globalPageMenus` field of the Renderer extension class (now `Renderer.LensExtension`) is removed.
Global pages can still be made accessible via the application menus and the status bar, as well as from the newly added Welcome menu. Global pages can still be made accessible via the application menus and the status bar, as well as from the newly added Welcome menu.
* The `clusterFeatures` field of the Renderer extension class (now `Renderer.LensExtension`) is removed. * The `clusterFeatures` field of the Renderer extension class (now `Renderer.LensExtension`) is removed.
Cluster features can still be implemented but Lens no longer dictates how the feature's lifecycle (install/upgrade/uninstall) is managed. Cluster features can still be implemented but Lens no longer dictates how a feature's lifecycle (install/upgrade/uninstall) is managed.
`Renderer.K8sApi.ResourceStack` provides the functionality to input and apply kubernetes resources to a cluster. `Renderer.K8sApi.ResourceStack` provides the functionality to input and apply kubernetes resources to a cluster.
It is up to the extension developer to manage the lifecycle (i.e. done automatically by the extension or allow the end-user to control via a `Renderer.LensExtension.entitySettings` entry). It is up to the extension developer to manage the lifecycle.
It could be applied automatically to a cluster by the extension or the end-user could be expected to install it, etc. from the cluster **Settings** page.
* Lens v5 now relies on mobx 6 for state management. Extensions that use mobx will need to be modified to work with mobx 6. * Lens v5 now relies on mobx 6 for state management. Extensions that use mobx will need to be modified to work with mobx 6.
See [Migrating from Mobx 4/5](https://mobx.js.org/migrating-from-4-or-5.html) for specific details. See [Migrating from Mobx 4/5](https://mobx.js.org/migrating-from-4-or-5.html) for specific details.

View File

@ -0,0 +1,5 @@
# Catalog (WIP)
## CatalogCategoryRegistry
## CatalogEntityRegistry

View File

@ -96,5 +96,7 @@ export default class SamplePageMainExtension extends Main.LensExtension {
When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`. When the menu item is clicked the `navigate()` method looks for and displays a global page with id `"myGlobalPage"`.
This page would be defined in your extension's `Renderer.LensExtension` implmentation (See [`Renderer.LensExtension`](renderer-extension.md)). This page would be defined in your extension's `Renderer.LensExtension` implmentation (See [`Renderer.LensExtension`](renderer-extension.md)).
### `addCatalogSource()` and `removeCatalogSource()` Methods
The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities). The `Main.LensExtension` class also provides the `addCatalogSource()` and `removeCatalogSource()` methods, for managing custom catalog items (or entities).
See the [`Catalog`](tbd) documentation for full details about the catalog. See the [`Catalog`](catalog.md) documentation for full details about the catalog.

View File

@ -1,4 +1,4 @@
# Renderer Extension # Renderer Extension (WIP)
The Renderer Extension API is the interface to Lens's renderer process. The Renderer Extension API is the interface to Lens's renderer process.
Lens runs in both the main and renderer processes. Lens runs in both the main and renderer processes.
@ -25,7 +25,7 @@ as well as catalog-related UI elements:
All UI elements are based on React components. All UI elements are based on React components.
As well you can add commands and protocol handlers: Finally, you can also add commands and protocol handlers:
* [Command palette commands](#commandpalettecommands) * [Command palette commands](#commandpalettecommands)
* [protocol handlers](protocol-handlers.md) * [protocol handlers](protocol-handlers.md)
@ -332,89 +332,7 @@ Global pages can be made available in the following ways:
* To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems). * To add global pages as an interactive element in the blue status bar along the bottom of the Lens UI, see [`statusBarItems`](#statusbaritems).
* To add global pages to the left side menu, see [`globalPageMenus`](#globalpagemenus). * To add global pages to the left side menu, see [`globalPageMenus`](#globalpagemenus).
### `globalPageMenus` ### `welcomeMenus`
`globalPageMenus` allows you to add global page menu items to the left nav.
By expanding on the above example, you can add a global page menu item to the `HelpExtension` definition:
```typescript
import { Renderer } from "@k8slens/extensions";
import { HelpIcon, HelpPage } from "./page"
import React from "react"
export default class HelpExtension extends Renderer.LensExtension {
globalPages = [
{
id: "help",
components: {
Page: () => <HelpPage extension={this}/>,
}
}
];
globalPageMenus = [
{
target: { pageId: "help" },
title: "Help",
components: {
Icon: HelpIcon,
}
},
];
}
```
`globalPageMenus` is an array of objects that satisfy the `PageMenuRegistration` interface.
This element defines how the global page menu item will appear and what it will do when you click it.
The properties of the `globalPageMenus` array objects are defined as follows:
* `target` links to the relevant global page using `pageId`.
* `pageId` takes the value of the relevant global page's `id` property.
* `title` sets the name of the global page menu item that will display as a tooltip in the left nav.
* `components` is used to set an icon that appears in the left nav.
The above example creates a "Help" icon menu item.
When users click the icon, the Lens UI will display the contents of `ExamplePage`.
This example requires the definition of another React-based component, `HelpIcon`.
Update `page.tsx` from the example above with the `HelpIcon` definition, as follows:
```typescript
import { Renderer } from "@k8slens/extensions";
import React from "react"
type IconProps = Renderer.Component.IconProps;
const {
Component: { Icon },
} = Renderer;
export function HelpIcon(props: IconProps) {
return <Icon {...props} material="help"/>
}
export class HelpPage extends React.Component<{ extension: Renderer.LensExtension }> {
render() {
return (
<div>
<p>Help</p>
</div>
)
}
}
```
Lens includes various built-in components available for extension developers to use.
One of these is the `Renderer.Component.Icon`, introduced in `HelpIcon`, which you can use to access any of the [icons](https://material.io/resources/icons/) available at [Material Design](https://material.io).
The property that `Renderer.Component.Icon` uses is defined as follows:
* `material` takes the name of the icon you want to use.
This is what the example will look like, including how the menu item will appear in the left nav:
![globalPageMenus](images/globalpagemenus.png)
### `appPreferences` ### `appPreferences`
The Lens **Preferences** page is a built-in global page. The Lens **Preferences** page is a built-in global page.
@ -525,6 +443,8 @@ Note that you can manage an extension's state data using an `ExtensionStore` obj
To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state. To simplify this guide, the example above defines a `preference` field in the `ExampleRendererExtension` class definition to hold the extension's state.
However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore). However, we recommend that you manage your extension's state data using [`ExtensionStore`](../stores#extensionstore).
### `topBarItems`
### `statusBarItems` ### `statusBarItems`
The status bar is the blue strip along the bottom of the Lens UI. The status bar is the blue strip along the bottom of the Lens UI.
@ -846,3 +766,17 @@ Construct the table using the `Renderer.Component.Table` and related elements.
For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods. For each pod the name, age, and status are obtained using the `Renderer.K8sApi.Pod` methods.
The table is constructed using the `Renderer.Component.Table` and related elements. The table is constructed using the `Renderer.Component.Table` and related elements.
See [Component documentation](https://docs.k8slens.dev/latest/extensions/api/modules/_renderer_api_components_/) for further details. See [Component documentation](https://docs.k8slens.dev/latest/extensions/api/modules/_renderer_api_components_/) for further details.
### `kubeObjectStatusTexts`
### `kubeWorkloadsOverviewItems`
### `entitySettings`
### `catalogEntityDetailItems`
### `commandPaletteCommands`
### `protocolHandlers`
See the [Protocol Handlers Guide](protocol-handlers.md)

View File

@ -1,4 +1,14 @@
# Resource Stack (Cluster Feature) # Resource Stack (Cluster Feature) (WIP)
Cluster features are Kubernetes resources that can be applied to and managed within the active cluster.
The `Renderer.K8sApi.ResourceStack` class provides the functionality to input and apply kubernetes resources to a cluster.
It is up to the extension developer to manage the lifecycle of the resource stack.
It could be applied automatically to a cluster by the extension or the end-user could be required to install it, etc. from the cluster **Settings** page.
!!! info
To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**.
The following example shows how to add a cluster feature using a resource stack as part of a `Renderer.LensExtension`:
```typescript ```typescript
import { Renderer, Common } from "@k8slens/extensions"; import { Renderer, Common } from "@k8slens/extensions";
@ -56,168 +66,90 @@ export class ExampleResourceStack {
} }
``` ```
The `ExampleResourceStack` cluster feature can be managed by the end-user on the cluster **Settings** page via a `Renderer.LensExtension.entitySettings` entry.
Cluster features are Kubernetes resources that can be applied to and managed within the active cluster.
They can be installed and uninstalled by the Lens user from the cluster **Settings** page.
!!! info
To access the cluster **Settings** page, right-click the relevant cluster in the left side menu and click **Settings**.
The following example shows how to add a cluster feature as part of a `LensRendererExtension`:
```typescript ```typescript
import { Renderer } from "@k8slens/extensions" import React from "react";
import { ExampleFeature } from "./src/example-feature" import { Common, Renderer } from "@k8slens/extensions";
import React from "react" import { observer } from "mobx-react";
import { computed, observable, makeObservable } from "mobx";
import { ExampleResourceStack } from "./example-resource-stack";
export default class ExampleFeatureExtension extends Renderer.LensExtension { const {
clusterFeatures = [
{
title: "Example Feature",
components: {
Description: () => {
return (
<span>
Enable an example feature.
</span>
)
}
},
feature: new ExampleFeature()
}
];
}
```
The properties of the `clusterFeatures` array objects are defined as follows:
* `title` and `components.Description` provide content that appears on the cluster settings page, in the **Features** section.
* `feature` specifies an instance which extends the abstract class `ClusterFeature.Feature`, and specifically implements the following methods:
```typescript
abstract install(cluster: Cluster): Promise<void>;
abstract upgrade(cluster: Cluster): Promise<void>;
abstract uninstall(cluster: Cluster): Promise<void>;
abstract updateStatus(cluster: Cluster): Promise<ClusterFeatureStatus>;
```
The four methods listed above are defined as follows:
* The `install()` method installs Kubernetes resources using the `applyResources()` method, or by directly accessing the [Kubernetes API](../api/README.md).
This method is typically called when a user indicates that they want to install the feature (i.e., by clicking **Install** for the feature in the cluster settings page).
* The `upgrade()` method upgrades the Kubernetes resources already installed, if they are relevant to the feature.
This method is typically called when a user indicates that they want to upgrade the feature (i.e., by clicking **Upgrade** for the feature in the cluster settings page).
* The `uninstall()` method uninstalls Kubernetes resources using the [Kubernetes API](../api/README.md).
This method is typically called when a user indicates that they want to uninstall the feature (i.e., by clicking **Uninstall** for the feature in the cluster settings page).
* The `updateStatus()` method provides the current status information in the `status` field of the `ClusterFeature.Feature` parent class.
Lens periodically calls this method to determine details about the feature's current status.
The implementation of this method should uninstall Kubernetes resources using the Kubernetes api (`K8sApi`)
Consider using the following properties with `updateStatus()`:
* `status.currentVersion` and `status.latestVersion` may be displayed by Lens in the feature's description.
* `status.installed` should be set to `true` if the feature is installed, and `false` otherwise.
* `status.canUpgrade` is set according to a rule meant to determine whether the feature can be upgraded.
This rule can involve `status.currentVersion` and `status.latestVersion`, if desired.
The following shows a very simple implementation of a `ClusterFeature`:
```typescript
import { Renderer, Common } from "@k8slens/extensions";
import * as path from "path";
const {
K8sApi: { K8sApi: {
ResourceStack, forCluster, StatefulSet, DaemonSet, Deployment,
forCluster, },
StorageClass, Component: {
Namespace, SubTitle, Button,
} }
} = Renderer; } = Renderer;
type ResourceStack = Renderer.K8sApi.ResourceStack; interface Props {
type Pod = Renderer.K8sApi.Pod; cluster: Common.Catalog.KubernetesCluster;
type KubernetesCluster = Common.Catalog.KubernetesCluster;
export interface MetricsStatus {
installed: boolean;
canUpgrade: boolean;
}
export class ExampleFeature {
protected stack: ResourceStack;
constructor(protected cluster: KubernetesCluster) {
this.stack = new ResourceStack(cluster, this.name);
} }
install(): Promise<string> { @observer
return this.stack.kubectlApplyFolder(path.join(__dirname, "../resources/")); export class ExampleResourceStackSettings extends React.Component<Props> {
constructor(props: Props) {
super(props);
makeObservable(this);
} }
upgrade(): Promise<string> { @observable installed = false;
return this.install(config); @observable inProgress = false;
feature: ExampleResourceStack;
async componentDidMount() {
this.feature = new ExampleResourceStack(this.props.cluster);
await this.updateFeatureState();
} }
async getStatus(): Promise<MetricsStatus> { async updateFeatureState() {
const status: MetricsStatus = { installed: false, canUpgrade: false}; this.installed = await this.feature.isInstalled();
}
async save() {
this.inProgress = true;
try { try {
const pod = forCluster(cluster, Pod); if (this.installed) {
const examplePod = await pod.get({name: "example-pod", namespace: "default"}); await this.feature.uninstall();
if (examplePod?.kind) {
status.installed = true;
status.currentVersion = examplePod.spec.containers[0].image.split(":")[1];
status.canUpgrade = true; // a real implementation would perform a check here that is relevant to the specific feature
} else { } else {
status.installed = false; await this.feature.install();
status.canUpgrade = false;
} }
} catch(e) { } finally {
if (e?.error?.code === 404) { this.inProgress = false;
status.installed = false;
status.canUpgrade = false; await this.updateFeatureState();
} }
} }
return status; @computed get buttonLabel() {
if (this.inProgress && this.installed) return "Uninstalling ...";
if (this.inProgress) return "Applying ...";
if (this.installed) {
return "Uninstall";
} }
async uninstall(): Promise<string> { return "Apply";
return this.stack.kubectlDeleteFolder(this.resourceFolder); }
render() {
return (
<>
<section>
<SubTitle title="Example Resource Stack" />
<Button
label={this.buttonLabel}
waiting={this.inProgress}
onClick={() => this.save()}
primary />
</section>
</>
);
} }
} }
``` ```
This example implements the `install()` method by invoking the helper `applyResources()` method.
`applyResources()` tries to apply all resources read from all files found in the folder path provided.
In this case the folder path is the `../resources` subfolder relative to the current source code's folder.
The file `../resources/example-pod.yml` could contain:
``` yaml
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
containers:
- name: example-pod
image: nginx
```
The example above implements the four methods as follows:
* It implements `upgrade()` by invoking the `install()` method.
Depending on the feature to be supported by an extension, upgrading may require additional and/or different steps.
* It implements `uninstall()` by utilizing the [Kubernetes API](../api/README.md) which Lens provides to delete the `example-pod` applied by the `install()` method.
* It implements `updateStatus()` by using the [Kubernetes API](../api/README.md) which Lens provides to determine whether the `example-pod` is installed, what version is associated with it, and whether it can be upgraded.
The implementation determines what the status is for a specific cluster feature.

View File

@ -1,6 +1,6 @@
{ {
"name": "kube-object-event-status", "name": "kube-object-event-status",
"version": "0.0.1", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "lens-metrics-cluster-feature", "name": "lens-metrics-cluster-feature",
"version": "0.0.1", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "lens-node-menu", "name": "lens-node-menu",
"version": "0.0.1", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,6 +1,6 @@
{ {
"name": "lens-pod-menu", "name": "lens-pod-menu",
"version": "0.0.1", "version": "0.1.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -22,6 +22,7 @@ nav:
- Generator: extensions/guides/generator.md - Generator: extensions/guides/generator.md
- Main Extension: extensions/guides/main-extension.md - Main Extension: extensions/guides/main-extension.md
- Renderer Extension: extensions/guides/renderer-extension.md - Renderer Extension: extensions/guides/renderer-extension.md
- Catalog: extensions/guides/catalog.md
- Resource Stack: extensions/guides/resource-stack.md - Resource Stack: extensions/guides/resource-stack.md
- Stores: extensions/guides/stores.md - Stores: extensions/guides/stores.md
- Working with MobX: extensions/guides/working-with-mobx.md - Working with MobX: extensions/guides/working-with-mobx.md