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

Merge branch 'master' into fix/keep_fresh_resource_in_details_view

This commit is contained in:
Roman 2023-03-10 14:27:49 +02:00
commit c27b1f423a
1062 changed files with 15851 additions and 7478 deletions

54
.github/workflows/cron-test.yaml vendored Normal file
View File

@ -0,0 +1,54 @@
name: Cron Test
on:
schedule:
- cron: "0 0 * * 1" # Run on the first day over every week
jobs:
test:
name: cron unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-11, windows-2019]
node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
if: runner.os == 'Linux'
run: |
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Get npm cache directory path
if: ${{ runner.os != 'Windows' }}
id: npm-cache-dir-path
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: nick-fields/retry@v2
name: Install dependencies
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
command: npm ci
- run: npm run test:unit
name: Run tests

View File

@ -7,14 +7,80 @@ on:
branches: branches:
- master - master
jobs: jobs:
test: integration-test:
name: ${{ matrix.type }} tests on ${{ matrix.os }} name: integration tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-20.04, macos-11, windows-2019] os: [ubuntu-20.04, macos-11, windows-2019]
type: [unit, smoke] node-version: [16.x]
steps:
- name: Checkout Release from lens
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Add the current IP address, long hostname and short hostname record to /etc/hosts file
if: runner.os == 'Linux'
run: |
echo -e "$(ip addr show eth0 | grep "inet\b" | awk '{print $2}' | cut -d/ -f1)\t$(hostname -f) $(hostname -s)" | sudo tee -a /etc/hosts
- name: Using Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Get npm cache directory path
if: ${{ runner.os != 'Windows' }}
id: npm-cache-dir-path
shell: bash
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
if: ${{ runner.os != 'Windows' }}
id: npm-cache # use this to check for `cache-hit` (`steps.npm-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.npm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- uses: nick-fields/retry@v2
name: Install dependencies
with:
timeout_minutes: 20
max_attempts: 3
retry_on: error
command: npm ci
- name: Install integration test dependencies
id: minikube
uses: medyagh/setup-minikube@master
with:
minikube-version: latest
if: ${{ runner.os == 'Linux' }}
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' npm run test:integration
name: Run Linux integration tests
if: ${{ runner.os == 'Linux' }}
- run: npm run test:integration
name: Run macOS integration tests
shell: bash
if: ${{ runner.os == 'macOS' }}
- run: npm run test:integration
name: Run Windows integration tests
if: ${{ runner.os == 'Windows' }}
unit-test:
name: unit tests on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
node-version: [16.x] node-version: [16.x]
steps: steps:
- name: Checkout Release from lens - name: Checkout Release from lens
@ -57,24 +123,3 @@ jobs:
- run: npm run test:unit - run: npm run test:unit
name: Run tests name: Run tests
if: ${{ matrix.type == 'unit' }}
- name: Install integration test dependencies
id: minikube
uses: medyagh/setup-minikube@master
with:
minikube-version: latest
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
- run: xvfb-run --auto-servernum --server-args='-screen 0, 1600x900x24' npm run test:integration
name: Run Linux integration tests
if: ${{ runner.os == 'Linux' && matrix.type == 'smoke' }}
- run: npm run test:integration
name: Run macOS integration tests
shell: bash
if: ${{ runner.os == 'macOS' && matrix.type == 'smoke' }}
- run: npm run test:integration
name: Run Windows integration tests
if: ${{ runner.os == 'Windows' && matrix.type == 'smoke' }}

View File

@ -1,7 +1,7 @@
# Lens Desktop Core ("OpenLens") # Lens Desktop Core ("OpenLens")
[![Build Status](https://github.com/lensapp/lens/actions/workflows/test.yml/badge.svg)](https://github.com/lensapp/lens/actions/workflows/test.yml) [![Build Status](https://github.com/lensapp/lens/actions/workflows/test.yml/badge.svg)](https://github.com/lensapp/lens/actions/workflows/test.yml)
[![Chat on Slack](https://img.shields.io/badge/chat-on%20slack-blue.svg?logo=slack&longCache=true&style=flat)](https://k8slens.dev/slack.html) <img src="https://upload.wikimedia.org/wikipedia/commons/1/17/Discourse_icon.svg" width=25>[Explore our Forums](https://forums.k8slens.dev)
## The Repository ## The Repository
@ -27,7 +27,7 @@ See [Development](https://docs.k8slens.dev/contributing/development/) page.
## Contributing ## Contributing
See [Contributing](https://docs.k8slens.dev/contributing/) page. See [Contributing](https://docs.k8slens.dev/contributing/contribute-to-lens/) page.
## License ## License

View File

@ -1,7 +1,7 @@
# Release Guide # Release Guide
Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`. Releases for this repository are made via running the `create-release-pr` script defined in the `package.json`.
All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relavent commits from master. All releases will be made by creating a PR which bumps the version field in the `package.json` and, if necessary, cherry pick the relevant commits from master.
## Prerequisites ## Prerequisites
@ -11,9 +11,13 @@ All releases will be made by creating a PR which bumps the version field in the
## Steps ## Steps
1. If you are making a minor or major release (or prereleases for one) make sure you are on the `master` branch. 1. If you are making a minor or major release (or prereleases of one) make sure you are on the `master` branch.
1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch. 1. If you are making a patch release (or a prerelease for one) make sure you are on the `release/v<MAJOR>.<MINOR>` branch.
1. Run `npm run create-release-pr <release-type>`. If you are making a subsequent prerelease release, provide the `--check-commits` flag. 1. Run `npm run create-release-pr`.
1. If you are checking the commits, type `y<ENTER>` to pick a commit, and `n<ENTER>` to skip it. You will want to skip the commits that were part of previous prerelease releases. 1. Pick the PRs that you want to include in this release using the keys listed.
1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you. 1. Once the PR is created, approved, and then merged the `Release Open Lens` workflow will create a tag and release for you.
1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it. 1. If you are making a major or minor release, create a `release/v<MAJOR>.<MINOR>` branch and push it to `origin` so that future patch releases can be made from it.
1. If you released a major or minor version, create a new patch milestone and move all bug issues to that milestone and all enhancement issues to the next minor milestone.
1. If you released a patch version, create a new patch milestone for the next patch version and move all the issues and PRs (open or closed) that weren't included in the current release to that milestone.
1. Close the milestone related to the release that was just made (if not a prerelease release).
1. If you released a patch version and it contains PRs that targeted `release/v<MAJOR>.<MINOR>` make a new PR targeting master and include all the relevant PRs as cherry-picks. This PR should have the `skip-changelog` label and have a milestone of the next minor.

View File

@ -7,15 +7,15 @@ To install your first extension you should goto the [extension page](lens://app/
This documentation describes: This documentation describes:
* How to build, run, test, and publish an extension. - How to build, run, test, and publish an extension.
* How to take full advantage of the Lens Extension API. - How to take full advantage of the Lens Extension API.
* Where to find [guides](extensions/guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started. - Where to find [guides](extensions/guides/README.md) and [code samples](https://github.com/lensapp/lens-extension-samples) to help get you started.
## What Extensions Can Do ## What Extensions Can Do
Here are some examples of what you can achieve with the Extension API: Here are some examples of what you can achieve with the Extension API:
* Add custom components & views in the UI - Extending the Lens Workbench - Add custom components & views in the UI - Extending the Lens Workbench
For an overview of the Lens Extension API, refer to the [Common Capabilities](extensions/capabilities/common-capabilities.md) page. [Extension Guides Overview](extensions/guides/README.md) also includes a list of code samples and guides that illustrate various ways of using the Lens Extension API. For an overview of the Lens Extension API, refer to the [Common Capabilities](extensions/capabilities/common-capabilities.md) page. [Extension Guides Overview](extensions/guides/README.md) also includes a list of code samples and guides that illustrate various ways of using the Lens Extension API.
@ -23,11 +23,11 @@ For an overview of the Lens Extension API, refer to the [Common Capabilities](ex
Here is what each section of the Lens Extension API docs can help you with: Here is what each section of the Lens Extension API docs can help you with:
* **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample. - **Getting Started** teaches fundamental concepts for building extensions with the Hello World sample.
* **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics. - **Extension Capabilities** dissects Lens's Extension API into smaller categories and points you to more detailed topics.
* **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API. - **Extension Guides** includes guides and code samples that explain specific usages of Lens Extension API.
* **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions. - **Testing and Publishing** includes in-depth guides on various extension development topics, such as testing and publishing extensions.
* **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics. - **API Reference** contains exhaustive references for the Lens Extension API, Contribution Points, and many other topics.
## What's New ## What's New
@ -45,7 +45,7 @@ See the [Lens v4 to v5 extension migration notes](extensions/extension-migration
## Looking for Help ## Looking for Help
If you have questions for extension development, try asking on the [Lens Dev Slack](http://k8slens.slack.com/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time. If you have questions for extension development, try asking on the [Lens Forums](http://forums.k8slens.dev/). It's a public chatroom for Lens developers, where Lens team members chime in from time to time.
To provide feedback on the documentation or issues with the Lens Extension API, create new issues at [lensapp/lens](https://github.com/lensapp/lens/issues). Please use the labels `area/documentation` and/or `area/extension`. To provide feedback on the documentation or issues with the Lens Extension API, create new issues at [lensapp/lens](https://github.com/lensapp/lens/issues). Please use the labels `area/documentation` and/or `area/extension`.

View File

@ -1,7 +1,7 @@
{ {
"$schema": "node_modules/lerna/schemas/lerna-schema.json", "$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true, "useWorkspaces": true,
"version": "6.4.0-beta.13", "version": "6.5.0-alpha.0",
"npmClient": "npm", "npmClient": "npm",
"npmClientArgs": [ "npmClientArgs": [
"--network-timeout=100000" "--network-timeout=100000"

View File

@ -10,33 +10,33 @@ edit_uri: ""
nav: nav:
- Overview: README.md - Overview: README.md
- Getting Started: - Getting Started:
- Overview: extensions/get-started/overview.md - Overview: extensions/get-started/overview.md
- Your First Extension: extensions/get-started/your-first-extension.md - Your First Extension: extensions/get-started/your-first-extension.md
- Extension Anatomy: extensions/get-started/anatomy.md - Extension Anatomy: extensions/get-started/anatomy.md
- Wrapping Up: extensions/get-started/wrapping-up.md - Wrapping Up: extensions/get-started/wrapping-up.md
- Extension Capabilities: - Extension Capabilities:
- Common Capabilities: extensions/capabilities/common-capabilities.md - Common Capabilities: extensions/capabilities/common-capabilities.md
- Styling: extensions/capabilities/styling.md - Styling: extensions/capabilities/styling.md
- Extension Guides: - Extension Guides:
- Overview: extensions/guides/README.md - Overview: extensions/guides/README.md
- 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 - Catalog: extensions/guides/catalog.md
- Resource Stack: extensions/guides/resource-stack.md - Resource Stack: extensions/guides/resource-stack.md
- Extending KubernetesCluster: extensions/guides/extending-kubernetes-cluster.md - Extending KubernetesCluster: extensions/guides/extending-kubernetes-cluster.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
- Protocol Handlers: extensions/guides/protocol-handlers.md - Protocol Handlers: extensions/guides/protocol-handlers.md
- IPC: extensions/guides/ipc.md - IPC: extensions/guides/ipc.md
- Testing and Publishing: - Testing and Publishing:
- Testing Extensions: extensions/testing-and-publishing/testing.md - Testing Extensions: extensions/testing-and-publishing/testing.md
- Publishing Extensions: extensions/testing-and-publishing/publishing.md - Publishing Extensions: extensions/testing-and-publishing/publishing.md
- API Reference: extensions/api/README.md - API Reference: extensions/api/README.md
theme: theme:
name: 'material' name: "material"
highlightjs: true highlightjs: true
language: 'en' language: "en"
custom_dir: docs/custom_theme custom_dir: docs/custom_theme
favicon: img/favicon.ico favicon: img/favicon.ico
logo: img/lens-logo-icon.svg logo: img/lens-logo-icon.svg
@ -79,9 +79,9 @@ extra:
- icon: fontawesome/brands/twitter - icon: fontawesome/brands/twitter
link: https://twitter.com/k8slens link: https://twitter.com/k8slens
name: Lens on Twitter name: Lens on Twitter
- icon: fontawesome/brands/slack - icon: fontawesome/brands/discourse
link: http://k8slens.slack.com/ link: https://forums.k8slens.dev/
name: Lens on Slack name: Lens Forums
- icon: fontawesome/solid/link - icon: fontawesome/solid/link
link: https://k8slens.dev/ link: https://k8slens.dev/
name: Lens Website name: Lens Website

4223
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,6 @@
"adr": "^1.4.3", "adr": "^1.4.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"lerna": "^6.5.1", "lerna": "^6.5.1",
"rimraf": "^4.1.2" "rimraf": "^4.3.1"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@k8slens/bump-version-for-cron", "name": "@k8slens/bump-version-for-cron",
"version": "6.4.0-cron.4db172da60", "version": "6.5.0-alpha.0",
"description": "CLI to bump the version to during a cron daily alpha release", "description": "CLI to bump the version to during a cron daily alpha release",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -23,7 +23,7 @@
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.61", "@swc/cli": "^0.1.61",
"@swc/core": "^1.3.35", "@swc/core": "^1.3.37",
"@types/node": "^16.18.11", "@types/node": "^16.18.11",
"@types/semver": "^7.3.13", "@types/semver": "^7.3.13",
"rimraf": "^4.1.2" "rimraf": "^4.1.2"

View File

@ -3,7 +3,7 @@
"productName": "", "productName": "",
"description": "Lens Desktop Core", "description": "Lens Desktop Core",
"homepage": "https://github.com/lensapp/lens", "homepage": "https://github.com/lensapp/lens",
"version": "6.4.0-beta.13", "version": "6.5.0-alpha.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/lensapp/lens.git" "url": "git+https://github.com/lensapp/lens.git"
@ -57,7 +57,7 @@
"test:unit": "jest --testPathIgnorePatterns integration", "test:unit": "jest --testPathIgnorePatterns integration",
"test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func", "test:watch": "func() { jest ${1} --watch --testPathIgnorePatterns integration; }; func",
"lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .", "lint": "PROD=true eslint --ext js,ts,tsx --max-warnings=0 .",
"lint:fix": "npm run lint --fix" "lint:fix": "npm run lint -- --fix"
}, },
"config": { "config": {
"k8sProxyVersion": "0.3.0", "k8sProxyVersion": "0.3.0",
@ -127,14 +127,14 @@
"@astronautlabs/jsonpath": "^1.1.0", "@astronautlabs/jsonpath": "^1.1.0",
"@hapi/call": "^9.0.1", "@hapi/call": "^9.0.1",
"@hapi/subtext": "^7.1.0", "@hapi/subtext": "^7.1.0",
"@k8slens/node-fetch": "^6.4.0-beta.13", "@k8slens/node-fetch": "^6.5.0-alpha.0",
"@kubernetes/client-node": "^0.18.1", "@kubernetes/client-node": "^0.18.1",
"@material-ui/styles": "^4.11.5", "@material-ui/styles": "^4.11.5",
"@ogre-tools/fp": "^12.0.1", "@ogre-tools/fp": "^15.1.2",
"@ogre-tools/injectable": "^12.0.1", "@ogre-tools/injectable": "^15.1.2",
"@ogre-tools/injectable-extension-for-auto-registration": "^12.0.1", "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
"@ogre-tools/injectable-extension-for-mobx": "^12.0.1", "@ogre-tools/injectable-extension-for-mobx": "^15.1.2",
"@ogre-tools/injectable-react": "^12.0.1", "@ogre-tools/injectable-react": "^15.1.2",
"@sentry/electron": "^3.0.8", "@sentry/electron": "^3.0.8",
"@sentry/integrations": "^6.19.3", "@sentry/integrations": "^6.19.3",
"@side/jest-runtime": "^1.1.0", "@side/jest-runtime": "^1.1.0",
@ -202,7 +202,7 @@
"@material-ui/lab": "^4.0.0-alpha.60", "@material-ui/lab": "^4.0.0-alpha.60",
"@sentry/types": "^6.19.7", "@sentry/types": "^6.19.7",
"@swc/cli": "^0.1.61", "@swc/cli": "^0.1.61",
"@swc/core": "^1.3.35", "@swc/core": "^1.3.37",
"@swc/jest": "^0.2.24", "@swc/jest": "^0.2.24",
"@testing-library/dom": "^7.31.2", "@testing-library/dom": "^7.31.2",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
@ -329,7 +329,11 @@
"xterm-addon-fit": "^0.5.0" "xterm-addon-fit": "^0.5.0"
}, },
"peerDependencies": { "peerDependencies": {
"@k8slens/application": "^6.4.0-beta.13", "@k8slens/application": "^6.5.0-alpha.0",
"@k8slens/application-for-electron-main": "^6.5.0-alpha.0",
"@k8slens/run-many": "^1.0.0",
"@k8slens/test-utils": "^1.0.0",
"@k8slens/utilities": "^1.0.0",
"@types/byline": "^4.2.33", "@types/byline": "^4.2.33",
"@types/chart.js": "^2.9.36", "@types/chart.js": "^2.9.36",
"@types/color": "^3.0.3", "@types/color": "^3.0.3",

View File

@ -8,8 +8,6 @@ import type { GetCustomKubeConfigFilePath } from "../app-paths/get-custom-kube-c
import getCustomKubeConfigFilePathInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; import getCustomKubeConfigFilePathInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable";
import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; import clusterStoreInjectable from "../cluster-store/cluster-store.injectable";
import type { DiContainer } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable";
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import assert from "assert"; import assert from "assert";
@ -27,6 +25,7 @@ import type { WriteFileSync } from "../fs/write-file-sync.injectable";
import writeFileSyncInjectable from "../fs/write-file-sync.injectable"; import writeFileSyncInjectable from "../fs/write-file-sync.injectable";
import type { WriteBufferSync } from "../fs/write-buffer-sync.injectable"; import type { WriteBufferSync } from "../fs/write-buffer-sync.injectable";
import writeBufferSyncInjectable from "../fs/write-buffer-sync.injectable"; import writeBufferSyncInjectable from "../fs/write-buffer-sync.injectable";
import { Cluster } from "../cluster/cluster";
// NOTE: this is intended to read the actual file system // NOTE: this is intended to read the actual file system
const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png"); const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png");
@ -58,7 +57,6 @@ users:
describe("cluster-store", () => { describe("cluster-store", () => {
let di: DiContainer; let di: DiContainer;
let clusterStore: ClusterStore; let clusterStore: ClusterStore;
let createCluster: CreateCluster;
let writeJsonSync: WriteJsonSync; let writeJsonSync: WriteJsonSync;
let writeFileSync: WriteFileSync; let writeFileSync: WriteFileSync;
let writeBufferSync: WriteBufferSync; let writeBufferSync: WriteBufferSync;
@ -67,15 +65,13 @@ describe("cluster-store", () => {
let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string; let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string;
beforeEach(async () => { beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
di.override(directoryForTempInjectable, () => "/some-temp-directory"); di.override(directoryForTempInjectable, () => "/some-temp-directory");
di.override(kubectlBinaryNameInjectable, () => "kubectl"); di.override(kubectlBinaryNameInjectable, () => "kubectl");
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64"); di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
di.override(normalizedPlatformInjectable, () => "darwin"); di.override(normalizedPlatformInjectable, () => "darwin");
createCluster = di.inject(createClusterInjectionToken);
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync = di.inject(writeJsonSyncInjectable); writeJsonSync = di.inject(writeJsonSyncInjectable);
writeFileSync = di.inject(writeFileSyncInjectable); writeFileSync = di.inject(writeFileSyncInjectable);
writeBufferSync = di.inject(writeBufferSyncInjectable); writeBufferSync = di.inject(writeBufferSyncInjectable);
@ -85,6 +81,8 @@ describe("cluster-store", () => {
describe("empty config", () => { describe("empty config", () => {
beforeEach(async () => { beforeEach(async () => {
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {}); writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {});
clusterStore = di.inject(clusterStoreInjectable); clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load(); clusterStore.load();
@ -92,7 +90,7 @@ describe("cluster-store", () => {
describe("with foo cluster added", () => { describe("with foo cluster added", () => {
beforeEach(() => { beforeEach(() => {
const cluster = createCluster({ const cluster = new Cluster({
id: "foo", id: "foo",
contextName: "foo", contextName: "foo",
preferences: { preferences: {
@ -198,6 +196,9 @@ describe("cluster-store", () => {
}, },
], ],
}); });
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clusterStore = di.inject(clusterStoreInjectable); clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load(); clusterStore.load();
}); });
@ -249,6 +250,9 @@ describe("cluster-store", () => {
}, },
], ],
}); });
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
clusterStore = di.inject(clusterStoreInjectable); clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load(); clusterStore.load();
}); });
@ -262,6 +266,10 @@ describe("cluster-store", () => {
describe("pre 3.6.0-beta.1 config with an existing cluster", () => { describe("pre 3.6.0-beta.1 config with an existing cluster", () => {
beforeEach(() => { beforeEach(() => {
di.override(storeMigrationVersionInjectable, () => "3.6.0");
getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable);
writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", { writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {
__internal__: { __internal__: {
migrations: { migrations: {
@ -281,16 +289,15 @@ describe("cluster-store", () => {
}); });
writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon); writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon);
di.override(storeMigrationVersionInjectable, () => "3.6.0");
clusterStore = di.inject(clusterStoreInjectable); clusterStore = di.inject(clusterStoreInjectable);
clusterStore.load(); clusterStore.load();
}); });
it("migrates to modern format with kubeconfig in a file", async () => { it("migrates to modern format with kubeconfig in a file", async () => {
const config = clusterStore.clustersList[0].kubeConfigPath; const configPath = clusterStore.clustersList[0].kubeConfigPath.get();
expect(readFileSync(config)).toBe(minimalValidKubeConfig); expect(readFileSync(configPath)).toBe(minimalValidKubeConfig);
}); });
it("migrates to modern format with icon not in file", async () => { it("migrates to modern format with icon not in file", async () => {

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainer } from "@ogre-tools/injectable";
import kubectlApplyAllInjectable from "../../main/kubectl/kubectl-apply-all.injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import type { KubernetesCluster } from "../catalog-entities";
import readDirectoryInjectable from "../fs/read-directory.injectable";
import readFileInjectable from "../fs/read-file.injectable";
import createResourceStackInjectable from "../k8s/create-resource-stack.injectable";
import appPathsStateInjectable from "../app-paths/app-paths-state.injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
describe("create resource stack tests", () => {
let di: DiContainer;
let cluster: KubernetesCluster;
beforeEach(async () => {
di = getDiForUnitTesting();
cluster = {
getId: () => "test-cluster",
} as any;
di.override(readDirectoryInjectable, () => () => Promise.resolve(["file1"]) as any);
di.override(readFileInjectable, () => () => Promise.resolve("filecontents"));
di.override(appPathsStateInjectable, () => ({
get: () => ({}),
}));
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
});
describe("kubectlApplyFolder", () => {
it("returns response", async () => {
di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({
callWasSuccessful: true as const,
response: "success",
}));
const createResourceStack = di.inject(createResourceStackInjectable);
const resourceStack = createResourceStack(cluster, "test");
const response = await resourceStack.kubectlApplyFolder("/foo/bar");
expect(response).toEqual("success");
});
it("throws on error", async () => {
di.override(kubectlApplyAllInjectable, () => () => Promise.resolve({
callWasSuccessful: false as const,
error: "No permissions",
}));
const createResourceStack = di.inject(createResourceStackInjectable);
const resourceStack = createResourceStack(cluster, "test");
await expect(() => resourceStack.kubectlApplyFolder("/foo/bar")).rejects.toThrow("No permissions");
});
});
});

View File

@ -43,7 +43,7 @@ describe("HotbarStore", () => {
let loggerMock: jest.Mocked<Logger>; let loggerMock: jest.Mocked<Logger>;
beforeEach(async () => { beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
testCluster = getMockCatalogEntity({ testCluster = getMockCatalogEntity({
apiVersion: "v1", apiVersion: "v1",

View File

@ -21,7 +21,7 @@ describe("user store tests", () => {
let di: DiContainer; let di: DiContainer;
beforeEach(async () => { beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
di.override(writeFileInjectable, () => () => Promise.resolve()); di.override(writeFileInjectable, () => () => Promise.resolve());
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
@ -30,9 +30,9 @@ describe("user store tests", () => {
get: () => "latest" as const, get: () => "latest" as const,
init: async () => {}, init: async () => {},
})); }));
await di.inject(defaultUpdateChannelInjectable).init(); await di.inject(defaultUpdateChannelInjectable).init();
userStore = di.inject(userStoreInjectable);
}); });
describe("for an empty config", () => { describe("for an empty config", () => {
@ -42,6 +42,8 @@ describe("user store tests", () => {
writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {}); writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {});
writeJsonSync("/some-directory-for-user-data/kube_config", {}); writeJsonSync("/some-directory-for-user-data/kube_config", {});
userStore = di.inject(userStoreInjectable);
userStore.load(); userStore.load();
}); });
@ -90,6 +92,8 @@ describe("user store tests", () => {
di.override(storeMigrationVersionInjectable, () => "10.0.0"); di.override(storeMigrationVersionInjectable, () => "10.0.0");
userStore = di.inject(userStoreInjectable);
userStore.load(); userStore.load();
}); });

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import pathToNpmCliInjectable from "./path-to-npm-cli.injectable"; import pathToNpmCliInjectable from "./path-to-npm-cli.injectable";
export default getGlobalOverride(pathToNpmCliInjectable, () => "/some/npm/cli/path"); export default getGlobalOverride(pathToNpmCliInjectable, () => "/some/npm/cli/path");

View File

@ -7,7 +7,7 @@ import type Config from "conf";
import type { Migrations, Options as ConfOptions } from "conf/dist/source/types"; import type { Migrations, Options as ConfOptions } from "conf/dist/source/types";
import type { IEqualsComparer } from "mobx"; import type { IEqualsComparer } from "mobx";
import { makeObservable, reaction } from "mobx"; import { makeObservable, reaction } from "mobx";
import { disposer, isPromiseLike, toJS } from "../utils"; import { disposer, isPromiseLike } from "@k8slens/utilities";
import { broadcastMessage } from "../ipc"; import { broadcastMessage } from "../ipc";
import isEqual from "lodash/isEqual"; import isEqual from "lodash/isEqual";
import { kebabCase } from "lodash"; import { kebabCase } from "lodash";
@ -16,6 +16,7 @@ import type { Logger } from "../logger";
import type { PersistStateToConfig } from "./save-to-file"; import type { PersistStateToConfig } from "./save-to-file";
import type { GetBasenameOfPath } from "../path/get-basename.injectable"; import type { GetBasenameOfPath } from "../path/get-basename.injectable";
import type { EnlistMessageChannelListener } from "../utils/channel/enlist-message-channel-listener-injection-token"; import type { EnlistMessageChannelListener } from "../utils/channel/enlist-message-channel-listener-injection-token";
import { toJS } from "../utils";
export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> { export interface BaseStoreParams<T> extends Omit<ConfOptions<T>, "migrations"> {
syncOptions?: { syncOptions?: {

View File

@ -7,7 +7,7 @@ import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable";
import type Conf from "conf/dist/source"; import type Conf from "conf/dist/source";
import type { Migrations } from "conf/dist/source/types"; import type { Migrations } from "conf/dist/source/types";
import loggerInjectable from "../logger.injectable"; import loggerInjectable from "../logger.injectable";
import { getOrInsert, iter } from "../utils"; import { getOrInsert, iter } from "@k8slens/utilities";
export interface MigrationDeclaration { export interface MigrationDeclaration {
version: string; version: string;

View File

@ -12,7 +12,7 @@ describe("kubernetesClusterCategory", () => {
let kubernetesClusterCategory: KubernetesClusterCategory; let kubernetesClusterCategory: KubernetesClusterCategory;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting();
kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable); kubernetesClusterCategory = di.inject(kubernetesClusterCategoryInjectable);
}); });

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token"; import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token";
import { GeneralEntity } from "../../index"; import { GeneralEntity } from "../../index";
import { buildURL } from "../../../utils/buildUrl"; import { buildURL } from "@k8slens/utilities";
import catalogRouteInjectable from "../../../front-end-routing/routes/catalog/catalog-route.injectable"; import catalogRouteInjectable from "../../../front-end-routing/routes/catalog/catalog-route.injectable";
const catalogCatalogEntityInjectable = getInjectable({ const catalogCatalogEntityInjectable = getInjectable({

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token"; import { generalCatalogEntityInjectionToken } from "../general-catalog-entity-injection-token";
import { GeneralEntity } from "../../index"; import { GeneralEntity } from "../../index";
import { buildURL } from "../../../utils/buildUrl"; import { buildURL } from "@k8slens/utilities";
import welcomeRouteInjectable from "../../../front-end-routing/routes/welcome/welcome-route.injectable"; import welcomeRouteInjectable from "../../../front-end-routing/routes/welcome/welcome-route.injectable";
const welcomeCatalogEntityInjectable = getInjectable({ const welcomeCatalogEntityInjectable = getInjectable({

View File

@ -13,6 +13,7 @@ import { requestClusterActivation, requestClusterDisconnection } from "../../ren
import KubeClusterCategoryIcon from "./icons/kubernetes.svg"; import KubeClusterCategoryIcon from "./icons/kubernetes.svg";
import getClusterByIdInjectable from "../cluster-store/get-by-id.injectable"; import getClusterByIdInjectable from "../cluster-store/get-by-id.injectable";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import clusterConnectionInjectable from "../../main/cluster/cluster-connection.injectable";
export interface KubernetesClusterPrometheusMetrics { export interface KubernetesClusterPrometheusMetrics {
address?: { address?: {
@ -79,8 +80,15 @@ export class KubernetesCluster<
if (app) { if (app) {
const di = getLegacyGlobalDiForExtensionApi(); const di = getLegacyGlobalDiForExtensionApi();
const getClusterById = di.inject(getClusterByIdInjectable); const getClusterById = di.inject(getClusterByIdInjectable);
const cluster = getClusterById(this.getId());
await getClusterById(this.getId())?.activate(); if (!cluster) {
return;
}
const connectionCluster = di.inject(clusterConnectionInjectable, cluster);
await connectionCluster.activate();
} else { } else {
await requestClusterActivation(this.getId(), false); await requestClusterActivation(this.getId(), false);
} }
@ -90,8 +98,15 @@ export class KubernetesCluster<
if (app) { if (app) {
const di = getLegacyGlobalDiForExtensionApi(); const di = getLegacyGlobalDiForExtensionApi();
const getClusterById = di.inject(getClusterByIdInjectable); const getClusterById = di.inject(getClusterByIdInjectable);
const cluster = getClusterById(this.getId());
getClusterById(this.getId())?.disconnect(); if (!cluster) {
return;
}
const connectionCluster = di.inject(clusterConnectionInjectable, cluster);
connectionCluster.disconnect();
} else { } else {
await requestClusterDisconnection(this.getId(), false); await requestClusterDisconnection(this.getId(), false);
} }
@ -127,7 +142,13 @@ export class KubernetesCluster<
context.menuItems.push({ context.menuItems.push({
title: "Disconnect", title: "Disconnect",
icon: "link_off", icon: "link_off",
onClick: () => requestClusterDisconnection(this.getId()), onClick: () => {
requestClusterDisconnection(this.getId());
broadcastMessage(
IpcRendererNavigationEvents.NAVIGATE_IN_APP,
"/catalog",
);
},
}); });
break; break;
case LensKubernetesClusterStatus.DISCONNECTED: case LensKubernetesClusterStatus.DISCONNECTED:

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { Environments, getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import { getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog";
import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity"; import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity";
import productNameInjectable from "../vars/product-name.injectable"; import productNameInjectable from "../vars/product-name.injectable";
@ -32,7 +32,7 @@ export class WebLink extends CatalogEntity<CatalogEntityMetadata, WebLinkStatus,
onContextMenuOpen(context: CatalogEntityContextMenuContext) { onContextMenuOpen(context: CatalogEntityContextMenuContext) {
// NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer // NOTE: this is safe because `onContextMenuOpen` is only supposed to be called in the renderer
const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi(Environments.renderer); const di = getEnvironmentSpecificLegacyGlobalDiForExtensionApi("renderer");
const productName = di.inject(productNameInjectable); const productName = di.inject(productNameInjectable);
const weblinkStore = di.inject(weblinkStoreInjectable); const weblinkStore = di.inject(weblinkStoreInjectable);

View File

@ -7,8 +7,8 @@ import EventEmitter from "events";
import type TypedEmitter from "typed-emitter"; import type TypedEmitter from "typed-emitter";
import { observable, makeObservable } from "mobx"; import { observable, makeObservable } from "mobx";
import { once } from "lodash"; import { once } from "lodash";
import type { Disposer } from "../utils"; import type { Disposer } from "@k8slens/utilities";
import { iter } from "../utils"; import { iter } from "@k8slens/utilities";
import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/+catalog/custom-category-columns"; import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/+catalog/custom-category-columns";
export type { CategoryColumnRegistration, TitleCellProps }; export type { CategoryColumnRegistration, TitleCellProps };
@ -241,7 +241,7 @@ export interface CatalogEntityMetadata extends EntityMetadataObject {
shortName?: string; shortName?: string;
description?: string; description?: string;
source?: string; source?: string;
labels: Record<string, string>; labels: Partial<Record<string, string>>;
} }
export interface CatalogEntityStatus { export interface CatalogEntityStatus {

View File

@ -5,8 +5,8 @@
import { action, computed, observable, makeObservable } from "mobx"; import { action, computed, observable, makeObservable } from "mobx";
import { once } from "lodash"; import { once } from "lodash";
import { iter, getOrInsertMap, strictSet } from "../utils"; import { iter, getOrInsertMap, strictSet } from "@k8slens/utilities";
import type { Disposer } from "../utils"; import type { Disposer } from "@k8slens/utilities";
import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity"; import type { CatalogCategory, CatalogEntityData, CatalogEntityKindData } from "./catalog-entity";
export type CategoryFilter = (category: CatalogCategory) => any; export type CategoryFilter = (category: CatalogCategory) => any;
@ -34,6 +34,10 @@ export class CatalogCategoryRegistry {
}; };
} }
getById(id: string) {
return iter.find(this.categories.values(), (category) => category.getId() === id);
}
@computed get items() { @computed get items() {
return Array.from(this.categories); return Array.from(this.categories);
} }

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import lensProxyCertificateInjectable from "./lens-proxy-certificate.injectable"; import lensProxyCertificateInjectable from "./lens-proxy-certificate.injectable";
export default getGlobalOverride(lensProxyCertificateInjectable, () => { export default getGlobalOverride(lensProxyCertificateInjectable, () => {

View File

@ -4,7 +4,6 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { ClusterStore } from "./cluster-store"; import { ClusterStore } from "./cluster-store";
import { createClusterInjectionToken } from "../cluster/create-cluster-injection-token";
import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; import readClusterConfigSyncInjectable from "./read-cluster-config.injectable";
import emitAppEventInjectable from "../app-event-bus/emit-event.injectable"; import emitAppEventInjectable from "../app-event-bus/emit-event.injectable";
import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable";
@ -23,7 +22,6 @@ const clusterStoreInjectable = getInjectable({
id: "cluster-store", id: "cluster-store",
instantiate: (di) => new ClusterStore({ instantiate: (di) => new ClusterStore({
createCluster: di.inject(createClusterInjectionToken),
readClusterConfigSync: di.inject(readClusterConfigSyncInjectable), readClusterConfigSync: di.inject(readClusterConfigSyncInjectable),
emitAppEvent: di.inject(emitAppEventInjectable), emitAppEvent: di.inject(emitAppEventInjectable),
directoryForUserData: di.inject(directoryForUserDataInjectable), directoryForUserData: di.inject(directoryForUserDataInjectable),

View File

@ -10,7 +10,6 @@ import { BaseStore } from "../base-store/base-store";
import { Cluster } from "../cluster/cluster"; import { Cluster } from "../cluster/cluster";
import { toJS } from "../utils"; import { toJS } from "../utils";
import type { ClusterModel, ClusterId } from "../cluster-types"; import type { ClusterModel, ClusterId } from "../cluster-types";
import type { CreateCluster } from "../cluster/create-cluster-injection-token";
import type { ReadClusterConfigSync } from "./read-cluster-config.injectable"; import type { ReadClusterConfigSync } from "./read-cluster-config.injectable";
import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable";
@ -19,7 +18,6 @@ export interface ClusterStoreModel {
} }
interface Dependencies extends BaseStoreDependencies { interface Dependencies extends BaseStoreDependencies {
createCluster: CreateCluster;
readClusterConfigSync: ReadClusterConfigSync; readClusterConfigSync: ReadClusterConfigSync;
emitAppEvent: EmitAppEvent; emitAppEvent: EmitAppEvent;
} }
@ -64,7 +62,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
const cluster = clusterOrModel instanceof Cluster const cluster = clusterOrModel instanceof Cluster
? clusterOrModel ? clusterOrModel
: this.dependencies.createCluster( : new Cluster(
clusterOrModel, clusterOrModel,
this.dependencies.readClusterConfigSync(clusterOrModel), this.dependencies.readClusterConfigSync(clusterOrModel),
); );
@ -87,7 +85,7 @@ export class ClusterStore extends BaseStore<ClusterStoreModel> {
if (cluster) { if (cluster) {
cluster.updateModel(clusterModel); cluster.updateModel(clusterModel);
} else { } else {
cluster = this.dependencies.createCluster( cluster = new Cluster(
clusterModel, clusterModel,
this.dependencies.readClusterConfigSync(clusterModel), this.dependencies.readClusterConfigSync(clusterModel),
); );

View File

@ -39,10 +39,6 @@ export const updateClusterModelChecker = Joi.object<UpdateClusterModel>({
contextName: Joi.string() contextName: Joi.string()
.required() .required()
.min(1), .min(1),
workspace: Joi.string()
.optional(),
workspaces: Joi.array()
.items(Joi.string()),
preferences: Joi.object(), preferences: Joi.object(),
metadata: Joi.object(), metadata: Joi.object(),
accessibleNamespaces: Joi.array() accessibleNamespaces: Joi.array()
@ -70,18 +66,6 @@ export interface ClusterModel {
/** Path to cluster kubeconfig */ /** Path to cluster kubeconfig */
kubeConfigPath: string; kubeConfigPath: string;
/**
* Workspace id
*
* @deprecated
*/
workspace?: string;
/**
* @deprecated this is used only for hotbar migrations from 4.2.X
*/
workspaces?: string[];
/** User context in kubeconfig */ /** User context in kubeconfig */
contextName: string; contextName: string;
@ -97,7 +81,7 @@ export interface ClusterModel {
/** /**
* Labels for the catalog entity * Labels for the catalog entity
*/ */
labels?: Record<string, string>; labels?: Partial<Record<string, string>>;
} }
/** /**
@ -206,6 +190,6 @@ export interface ClusterState {
ready: boolean; ready: boolean;
isAdmin: boolean; isAdmin: boolean;
allowedNamespaces: string[]; allowedNamespaces: string[];
allowedResources: string[]; resourcesToShow: string[];
isGlobalWatchEnabled: boolean; isGlobalWatchEnabled: boolean;
} }

View File

@ -6,7 +6,6 @@
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node"; import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
import { AuthorizationV1Api } from "@kubernetes/client-node"; import { AuthorizationV1Api } from "@kubernetes/client-node";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { Logger } from "../logger";
import loggerInjectable from "../logger.injectable"; import loggerInjectable from "../logger.injectable";
/** /**
@ -19,41 +18,33 @@ export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean
/** /**
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster * @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
*/ */
export type AuthorizationReview = (proxyConfig: KubeConfig) => CanI; export type CreateAuthorizationReview = (proxyConfig: KubeConfig) => CanI;
interface Dependencies { const createAuthorizationReviewInjectable = getInjectable({
logger: Logger;
}
const authorizationReview = ({ logger }: Dependencies): AuthorizationReview => {
return (proxyConfig) => {
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
try {
const { body } = await api.createSelfSubjectAccessReview({
apiVersion: "authorization.k8s.io/v1",
kind: "SelfSubjectAccessReview",
spec: { resourceAttributes },
});
return body.status?.allowed ?? false;
} catch (error) {
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
return false;
}
};
};
};
const authorizationReviewInjectable = getInjectable({
id: "authorization-review", id: "authorization-review",
instantiate: (di) => { instantiate: (di): CreateAuthorizationReview => {
const logger = di.inject(loggerInjectable); const logger = di.inject(loggerInjectable);
return authorizationReview({ logger }); return (proxyConfig) => {
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
return async (resourceAttributes: V1ResourceAttributes): Promise<boolean> => {
try {
const { body } = await api.createSelfSubjectAccessReview({
apiVersion: "authorization.k8s.io/v1",
kind: "SelfSubjectAccessReview",
spec: { resourceAttributes },
});
return body.status?.allowed ?? false;
} catch (error) {
logger.error(`[AUTHORIZATION-REVIEW]: failed to create access review: ${error}`, { resourceAttributes });
return false;
}
};
};
}, },
}); });
export default authorizationReviewInjectable; export default createAuthorizationReviewInjectable;

View File

@ -3,164 +3,74 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { action, comparer, computed, makeObservable, observable, reaction, runInAction, when } from "mobx"; import { computed, observable, toJS, runInAction } from "mobx";
import type { ClusterContextHandler } from "../../main/context-handler/context-handler"; import type { KubeApiResource } from "../rbac";
import type { KubeConfig } from "@kubernetes/client-node"; import type { ClusterState, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, ClusterConfigData } from "../cluster-types";
import { HttpError } from "@kubernetes/client-node"; import { ClusterMetadataKey, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
import type { Kubectl } from "../../main/kubectl/kubectl"; import type { IObservableValue } from "mobx";
import type { KubeconfigManager } from "../../main/kubeconfig-manager/kubeconfig-manager"; import { replaceObservableObject } from "../utils/replace-observable-object";
import type { KubeApiResource, KubeApiResourceDescriptor } from "../rbac"; import { pick } from "lodash";
import { formatKubeApiResource } from "../rbac";
import plimit from "p-limit";
import type { ClusterState, ClusterMetricsResourceType, ClusterId, ClusterMetadata, ClusterModel, ClusterPreferences, ClusterPrometheusPreferences, UpdateClusterModel, KubeAuthUpdate, ClusterConfigData } from "../cluster-types";
import { ClusterMetadataKey, initialNodeShellImage, ClusterStatus, clusterModelIdChecker, updateClusterModelChecker } from "../cluster-types";
import { disposer, isDefined, isRequestError, toJS } from "../utils";
import { clusterListNamespaceForbiddenChannel } from "../ipc/cluster";
import type { CanI } from "./authorization-review.injectable";
import type { ListNamespaces } from "./list-namespaces.injectable";
import assert from "assert";
import type { Logger } from "../logger";
import type { BroadcastMessage } from "../ipc/broadcast-message.injectable";
import type { LoadConfigfromFile } from "../kube-helpers/load-config-from-file.injectable";
import type { CanListResource, RequestNamespaceListPermissions, RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
import type { RequestApiResources } from "../../main/cluster/request-api-resources.injectable";
import type { DetectClusterMetadata } from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
import type { FalibleOnlyClusterMetadataDetector } from "../../main/cluster-detectors/token";
export interface ClusterDependencies { export class Cluster {
readonly directoryForKubeConfigs: string;
readonly logger: Logger;
readonly clusterVersionDetector: FalibleOnlyClusterMetadataDetector;
detectClusterMetadata: DetectClusterMetadata;
createKubeconfigManager: (cluster: Cluster) => KubeconfigManager;
createContextHandler: (cluster: Cluster) => ClusterContextHandler;
createKubectl: (clusterVersion: string) => Kubectl;
createAuthorizationReview: (config: KubeConfig) => CanI;
requestApiResources: RequestApiResources;
requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor;
createListNamespaces: (config: KubeConfig) => ListNamespaces;
broadcastMessage: BroadcastMessage;
loadConfigfromFile: LoadConfigfromFile;
}
/**
* Cluster
*
* @beta
*/
export class Cluster implements ClusterModel {
/** Unique id for a cluster */
public readonly id: ClusterId;
private kubeCtl: Kubectl | undefined;
/** /**
* Context handler * Unique id for a cluster
*
* @internal
*/ */
protected readonly _contextHandler: ClusterContextHandler | undefined; readonly id: ClusterId;
protected readonly _proxyKubeconfigManager: KubeconfigManager | undefined;
protected readonly eventsDisposer = disposer();
protected activated = false;
public get contextHandler() {
// TODO: remove these once main/renderer are seperate classes
assert(this._contextHandler, "contextHandler is only defined in the main environment");
return this._contextHandler;
}
protected get proxyKubeconfigManager() {
// TODO: remove these once main/renderer are seperate classes
assert(this._proxyKubeconfigManager, "proxyKubeconfigManager is only defined in the main environment");
return this._proxyKubeconfigManager;
}
get whenReady() {
return when(() => this.ready);
}
/** /**
* Kubeconfig context name * Kubeconfig context name
*
* @observable
*/ */
@observable contextName!: string; readonly contextName = observable.box() as IObservableValue<string>;
/** /**
* Path to kubeconfig * Path to kubeconfig
*
* @observable
*/ */
@observable kubeConfigPath!: string; readonly kubeConfigPath = observable.box() as IObservableValue<string>;
/**
* @deprecated
*/
@observable workspace?: string;
/**
* @deprecated
*/
@observable workspaces?: string[];
/** /**
* Kubernetes API server URL * Kubernetes API server URL
*
* @observable
*/ */
@observable apiUrl: string; // cluster server url readonly apiUrl: IObservableValue<string>;
/** /**
* Is cluster online * Describes if we can detect that cluster is online
*
* @observable
*/ */
@observable online = false; // describes if we can detect that cluster is online readonly online = observable.box(false);
/** /**
* Can user access cluster resources * Describes if user is able to access cluster resources
*
* @observable
*/ */
@observable accessible = false; // if user is able to access cluster resources readonly accessible = observable.box(false);
/** /**
* Is cluster instance in usable state * Is cluster instance in usable state
*
* @observable
*/ */
@observable ready = false; // cluster is in usable state readonly ready = observable.box(false);
/**
* Is cluster currently reconnecting
*
* @observable
*/
@observable reconnecting = false;
/** /**
* Is cluster disconnected. False if user has selected to connect. * Is cluster disconnected. False if user has selected to connect.
*
* @observable
*/ */
@observable disconnected = true; readonly disconnected = observable.box(true);
/** /**
* Does user have admin like access * Does user have admin like access
*
* @observable
*/ */
@observable isAdmin = false; readonly isAdmin = observable.box(false);
/** /**
* Global watch-api accessibility , e.g. "/api/v1/services?watch=1" * Global watch-api accessibility , e.g. "/api/v1/services?watch=1"
*
* @observable
*/ */
@observable isGlobalWatchEnabled = false; readonly isGlobalWatchEnabled = observable.box(false);
/** /**
* Preferences * Preferences
*
* @observable
*/ */
@observable preferences: ClusterPreferences = {}; readonly preferences = observable.object<ClusterPreferences>({});
/** /**
* Metadata * Metadata
*
* @observable
*/ */
@observable metadata: ClusterMetadata = {}; readonly metadata = observable.object<ClusterMetadata>({});
/** /**
* List of allowed namespaces verified via K8S::SelfSubjectAccessReview api * List of allowed namespaces verified via K8S::SelfSubjectAccessReview api
@ -172,73 +82,47 @@ export class Cluster implements ClusterModel {
*/ */
readonly accessibleNamespaces = observable.array<string>(); readonly accessibleNamespaces = observable.array<string>();
private readonly knownResources = observable.array<KubeApiResource>(); /**
* The list of all known resources associated with this cluster
*/
readonly knownResources = observable.array<KubeApiResource>();
// The formatting of this is `group.name` or `name` (if in core) /**
private readonly allowedResources = observable.set<string>(); * The formatting of this is `group.name` or `name` (if in core)
*/
readonly resourcesToShow = observable.set<string>();
/** /**
* Labels for the catalog entity * Labels for the catalog entity
*/ */
@observable labels: Record<string, string> = {}; readonly labels = observable.object<Partial<Record<string, string>>>({});
/** /**
* Is cluster available * Is cluster available
*
* @computed
*/ */
@computed get available() { readonly available = computed(() => this.accessible.get() && !this.disconnected.get());
return this.accessible && !this.disconnected;
}
/** /**
* Cluster name * Cluster name
*
* @computed
*/ */
@computed get name() { readonly name = computed(() => this.preferences.clusterName || this.contextName.get());
return this.preferences.clusterName || this.contextName;
}
/** /**
* The detected kubernetes distribution * The detected kubernetes distribution
*/ */
@computed get distribution(): string { readonly distribution = computed(() => this.metadata[ClusterMetadataKey.DISTRIBUTION]?.toString() || "unknown");
return this.metadata[ClusterMetadataKey.DISTRIBUTION]?.toString() || "unknown";
}
/** /**
* The detected kubernetes version * The detected kubernetes version
*/ */
@computed get version(): string { readonly version = computed(() => this.metadata[ClusterMetadataKey.VERSION]?.toString() || "unknown");
return this.metadata[ClusterMetadataKey.VERSION]?.toString() || "unknown";
}
/** /**
* Prometheus preferences * Prometheus preferences
*
* @computed
* @internal
*/ */
@computed get prometheusPreferences(): ClusterPrometheusPreferences { readonly prometheusPreferences = computed(() => pick(toJS(this.preferences), "prometheus", "prometheusProvider") as ClusterPrometheusPreferences);
const { prometheus, prometheusProvider } = this.preferences;
return toJS({ prometheus, prometheusProvider });
}
/**
* defaultNamespace preference
*
* @computed
* @internal
*/
@computed get defaultNamespace(): string | undefined {
return this.preferences.defaultNamespace;
}
constructor(private readonly dependencies: ClusterDependencies, { id, ...model }: ClusterModel, configData: ClusterConfigData) {
makeObservable(this);
constructor({ id, ...model }: ClusterModel, configData: ClusterConfigData) {
const { error } = clusterModelIdChecker.validate({ id }); const { error } = clusterModelIdChecker.validate({ id });
if (error) { if (error) {
@ -247,16 +131,7 @@ export class Cluster implements ClusterModel {
this.id = id; this.id = id;
this.updateModel(model); this.updateModel(model);
this.apiUrl = configData.clusterServerUrl; this.apiUrl = observable.box(configData.clusterServerUrl);
// for the time being, until renderer gets its own cluster type
this._contextHandler = this.dependencies.createContextHandler(this);
this._proxyKubeconfigManager = this.dependencies.createKubeconfigManager(this);
this.dependencies.logger.debug(`[CLUSTER]: Cluster init success`, {
id: this.id,
context: this.contextName,
apiUrl: this.apiUrl,
});
} }
/** /**
@ -264,7 +139,7 @@ export class Cluster implements ClusterModel {
* *
* @param model * @param model
*/ */
@action updateModel(model: UpdateClusterModel) { updateModel(model: UpdateClusterModel) {
// Note: do not assign ID as that should never be updated // Note: do not assign ID as that should never be updated
const { error } = updateClusterModelChecker.validate(model, { allowUnknown: true }); const { error } = updateClusterModelChecker.validate(model, { allowUnknown: true });
@ -273,448 +148,83 @@ export class Cluster implements ClusterModel {
throw error; throw error;
} }
this.kubeConfigPath = model.kubeConfigPath;
this.contextName = model.contextName;
if (model.workspace) {
this.workspace = model.workspace;
}
if (model.workspaces) {
this.workspaces = model.workspaces;
}
if (model.preferences) {
this.preferences = model.preferences;
}
if (model.metadata) {
this.metadata = model.metadata;
}
if (model.accessibleNamespaces) {
this.accessibleNamespaces.replace(model.accessibleNamespaces);
}
if (model.labels) {
this.labels = model.labels;
}
}
/**
* @internal
*/
protected bindEvents() {
this.dependencies.logger.info(`[CLUSTER]: bind events`, this.getMeta());
const refreshTimer = setInterval(() => !this.disconnected && this.refresh(), 30000); // every 30s
const refreshMetadataTimer = setInterval(() => this.available && this.refreshAccessibilityAndMetadata(), 900000); // every 15 minutes
this.eventsDisposer.push(
reaction(
() => this.prometheusPreferences,
prefs => this.contextHandler.setupPrometheus(prefs),
{ equals: comparer.structural },
),
() => clearInterval(refreshTimer),
() => clearInterval(refreshMetadataTimer),
reaction(() => this.defaultNamespace, () => this.recreateProxyKubeconfig()),
);
}
/**
* @internal
*/
protected async recreateProxyKubeconfig() {
this.dependencies.logger.info("[CLUSTER]: Recreating proxy kubeconfig");
try {
await this.proxyKubeconfigManager.clear();
await this.getProxyKubeconfig();
} catch (error) {
this.dependencies.logger.error(`[CLUSTER]: failed to recreate proxy kubeconfig`, error);
}
}
/**
* @param force force activation
* @internal
*/
@action
async activate(force = false) {
if (this.activated && !force) {
return;
}
this.dependencies.logger.info(`[CLUSTER]: activate`, this.getMeta());
if (!this.eventsDisposer.length) {
this.bindEvents();
}
if (this.disconnected || !this.accessible) {
try {
this.broadcastConnectUpdate("Starting connection ...");
await this.reconnect();
} catch (error) {
this.broadcastConnectUpdate(`Failed to start connection: ${error}`, "error");
return;
}
}
try {
this.broadcastConnectUpdate("Refreshing connection status ...");
await this.refreshConnectionStatus();
} catch (error) {
this.broadcastConnectUpdate(`Failed to connection status: ${error}`, "error");
return;
}
if (this.accessible) {
try {
this.broadcastConnectUpdate("Refreshing cluster accessibility ...");
await this.refreshAccessibility();
} catch (error) {
this.broadcastConnectUpdate(`Failed to refresh accessibility: ${error}`, "error");
return;
}
// download kubectl in background, so it's not blocking dashboard
this.ensureKubectl()
.catch(error => this.dependencies.logger.warn(`[CLUSTER]: failed to download kubectl for clusterId=${this.id}`, error));
this.broadcastConnectUpdate("Connected, waiting for view to load ...");
}
this.activated = true;
}
/**
* @internal
*/
async ensureKubectl() {
this.kubeCtl ??= this.dependencies.createKubectl(this.version);
await this.kubeCtl.ensureKubectl();
return this.kubeCtl;
}
/**
* @internal
*/
@action
async reconnect() {
this.dependencies.logger.info(`[CLUSTER]: reconnect`, this.getMeta());
await this.contextHandler?.restartServer();
this.disconnected = false;
}
/**
* @internal
*/
@action disconnect(): void {
if (this.disconnected) {
return void this.dependencies.logger.debug("[CLUSTER]: already disconnected", { id: this.id });
}
this.dependencies.logger.info(`[CLUSTER]: disconnecting`, { id: this.id });
this.eventsDisposer();
this.contextHandler?.stopServer();
this.disconnected = true;
this.online = false;
this.accessible = false;
this.ready = false;
this.activated = false;
this.allowedNamespaces.clear();
this.dependencies.logger.info(`[CLUSTER]: disconnected`, { id: this.id });
}
/**
* @internal
*/
@action
async refresh() {
this.dependencies.logger.info(`[CLUSTER]: refresh`, this.getMeta());
await this.refreshConnectionStatus();
}
/**
* @internal
*/
@action
async refreshAccessibilityAndMetadata() {
await this.refreshAccessibility();
await this.refreshMetadata();
}
/**
* @internal
*/
async refreshMetadata() {
this.dependencies.logger.info(`[CLUSTER]: refreshMetadata`, this.getMeta());
const newMetadata = await this.dependencies.detectClusterMetadata(this);
runInAction(() => { runInAction(() => {
this.metadata = { this.kubeConfigPath.set(model.kubeConfigPath);
...this.metadata, this.contextName.set(model.contextName);
...newMetadata,
};
});
}
/** if (model.preferences) {
* @internal replaceObservableObject(this.preferences, model.preferences);
*/
private async refreshAccessibility(): Promise<void> {
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.getMeta());
const proxyConfig = await this.getProxyKubeconfig();
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig);
this.isAdmin = await canI({
namespace: "kube-system",
resource: "*",
verb: "create",
});
this.isGlobalWatchEnabled = await canI({
verb: "watch",
resource: "*",
});
this.allowedNamespaces.replace(await this.requestAllowedNamespaces(proxyConfig));
const knownResources = await this.dependencies.requestApiResources(this);
if (knownResources.callWasSuccessful) {
this.knownResources.replace(knownResources.response);
} else if (this.knownResources.length > 0) {
this.dependencies.logger.warn(`[CLUSTER]: failed to list KUBE resources, sticking with previous list`);
} else {
this.dependencies.logger.warn(`[CLUSTER]: failed to list KUBE resources for the first time, blocking connection to cluster...`);
this.broadcastConnectUpdate("Failed to list kube API resources, please reconnect...", "error");
}
this.allowedResources.replace(await this.getAllowedResources(requestNamespaceListPermissions));
this.ready = this.knownResources.length > 0;
}
/**
* @internal
*/
@action
async refreshConnectionStatus() {
const connectionStatus = await this.getConnectionStatus();
this.online = connectionStatus > ClusterStatus.Offline;
this.accessible = connectionStatus == ClusterStatus.AccessGranted;
}
async getKubeconfig(): Promise<KubeConfig> {
const { config } = await this.dependencies.loadConfigfromFile(this.kubeConfigPath);
return config;
}
/**
* @internal
*/
async getProxyKubeconfig(): Promise<KubeConfig> {
const proxyKCPath = await this.getProxyKubeconfigPath();
const { config } = await this.dependencies.loadConfigfromFile(proxyKCPath);
return config;
}
/**
* @internal
*/
async getProxyKubeconfigPath(): Promise<string> {
return this.proxyKubeconfigManager.getPath();
}
protected async getConnectionStatus(): Promise<ClusterStatus> {
try {
const versionData = await this.dependencies.clusterVersionDetector.detect(this);
this.metadata.version = versionData.value;
return ClusterStatus.AccessGranted;
} catch (error) {
this.dependencies.logger.error(`[CLUSTER]: Failed to connect to "${this.contextName}": ${error}`);
if (isRequestError(error)) {
if (error.statusCode) {
if (error.statusCode >= 400 && error.statusCode < 500) {
this.broadcastConnectUpdate("Invalid credentials", "error");
return ClusterStatus.AccessDenied;
}
const message = String(error.error || error.message) || String(error);
this.broadcastConnectUpdate(message, "error");
return ClusterStatus.Offline;
}
if (error.failed === true) {
if (error.timedOut === true) {
this.broadcastConnectUpdate("Connection timed out", "error");
return ClusterStatus.Offline;
}
this.broadcastConnectUpdate("Failed to fetch credentials", "error");
return ClusterStatus.AccessDenied;
}
const message = String(error.error || error.message) || String(error);
this.broadcastConnectUpdate(message, "error");
} else if (error instanceof Error || typeof error === "string") {
this.broadcastConnectUpdate(`${error}`, "error");
} else {
this.broadcastConnectUpdate("Unknown error has occurred", "error");
} }
return ClusterStatus.Offline; if (model.metadata) {
} replaceObservableObject(this.metadata, model.metadata);
}
if (model.accessibleNamespaces) {
this.accessibleNamespaces.replace(model.accessibleNamespaces);
}
if (model.labels) {
replaceObservableObject(this.labels, model.labels);
}
});
} }
toJSON(): ClusterModel { toJSON(): ClusterModel {
return toJS({ return {
id: this.id, id: this.id,
contextName: this.contextName, contextName: this.contextName.get(),
kubeConfigPath: this.kubeConfigPath, kubeConfigPath: this.kubeConfigPath.get(),
workspace: this.workspace, preferences: toJS(this.preferences),
workspaces: this.workspaces, metadata: toJS(this.metadata),
preferences: this.preferences, accessibleNamespaces: this.accessibleNamespaces.toJSON(),
metadata: this.metadata, labels: toJS(this.labels),
accessibleNamespaces: this.accessibleNamespaces, };
labels: this.labels,
});
} }
/** /**
* Serializable cluster-state used for sync btw main <-> renderer * Serializable cluster-state used for sync btw main <-> renderer
*/ */
getState(): ClusterState { getState(): ClusterState {
return toJS({ return {
apiUrl: this.apiUrl, apiUrl: this.apiUrl.get(),
online: this.online, online: this.online.get(),
ready: this.ready, ready: this.ready.get(),
disconnected: this.disconnected, disconnected: this.disconnected.get(),
accessible: this.accessible, accessible: this.accessible.get(),
isAdmin: this.isAdmin, isAdmin: this.isAdmin.get(),
allowedNamespaces: this.allowedNamespaces, allowedNamespaces: this.allowedNamespaces.toJSON(),
allowedResources: [...this.allowedResources], resourcesToShow: this.resourcesToShow.toJSON(),
isGlobalWatchEnabled: this.isGlobalWatchEnabled, isGlobalWatchEnabled: this.isGlobalWatchEnabled.get(),
}); };
} }
/** /**
* @internal
* @param state cluster state * @param state cluster state
*/ */
@action setState(state: ClusterState) { setState(state: ClusterState) {
this.accessible = state.accessible; runInAction(() => {
this.allowedNamespaces.replace(state.allowedNamespaces); this.accessible.set(state.accessible);
this.allowedResources.replace(state.allowedResources); this.allowedNamespaces.replace(state.allowedNamespaces);
this.apiUrl = state.apiUrl; this.resourcesToShow.replace(state.resourcesToShow);
this.disconnected = state.disconnected; this.apiUrl.set(state.apiUrl);
this.isAdmin = state.isAdmin; this.disconnected.set(state.disconnected);
this.isGlobalWatchEnabled = state.isGlobalWatchEnabled; this.isAdmin.set(state.isAdmin);
this.online = state.online; this.isGlobalWatchEnabled.set(state.isGlobalWatchEnabled);
this.ready = state.ready; this.online.set(state.online);
this.ready.set(state.ready);
});
} }
// get cluster system meta, e.g. use in "logger" // get cluster system meta, e.g. use in "logger"
getMeta() { getMeta() {
return { return {
id: this.id, id: this.id,
name: this.contextName, name: this.contextName.get(),
ready: this.ready, ready: this.ready.get(),
online: this.online, online: this.online.get(),
accessible: this.accessible, accessible: this.accessible.get(),
disconnected: this.disconnected, disconnected: this.disconnected.get(),
}; };
} }
/**
* broadcast an authentication update concerning this cluster
* @internal
*/
broadcastConnectUpdate(message: string, level: KubeAuthUpdate["level"] = "info"): void {
const update: KubeAuthUpdate = { message, level };
this.dependencies.logger.debug(`[CLUSTER]: broadcasting connection update`, { ...update, meta: this.getMeta() });
this.dependencies.broadcastMessage(`cluster:${this.id}:connection-update`, update);
}
protected async requestAllowedNamespaces(proxyConfig: KubeConfig) {
if (this.accessibleNamespaces.length) {
return this.accessibleNamespaces;
}
try {
const listNamespaces = this.dependencies.createListNamespaces(proxyConfig);
return await listNamespaces();
} catch (error) {
const ctx = proxyConfig.getContextObject(this.contextName);
const namespaceList = [ctx?.namespace].filter(isDefined);
if (namespaceList.length === 0 && error instanceof HttpError && error.statusCode === 403) {
const { response } = error as HttpError & { response: { body: unknown }};
this.dependencies.logger.info("[CLUSTER]: listing namespaces is forbidden, broadcasting", { clusterId: this.id, error: response.body });
this.dependencies.broadcastMessage(clusterListNamespaceForbiddenChannel, this.id);
}
return namespaceList;
}
}
protected async getAllowedResources(requestNamespaceListPermissions: RequestNamespaceListPermissions) {
if (!this.allowedNamespaces.length || !this.knownResources.length) {
return [];
}
try {
const apiLimit = plimit(5); // 5 concurrent api requests
const canListResourceCheckers = await Promise.all((
this.allowedNamespaces.map(namespace => apiLimit(() => requestNamespaceListPermissions(namespace)))
));
const canListNamespacedResource: CanListResource = (resource) => canListResourceCheckers.some(fn => fn(resource));
return this.knownResources
.filter(canListNamespacedResource)
.map(formatKubeApiResource);
} catch (error) {
return [];
}
}
shouldShowResource(resource: KubeApiResourceDescriptor): boolean {
return this.allowedResources.has(formatKubeApiResource(resource));
}
isMetricHidden(resource: ClusterMetricsResourceType): boolean {
return Boolean(this.preferences.hiddenMetrics?.includes(resource));
}
get nodeShellImage(): string {
return this.preferences?.nodeShellImage || initialNodeShellImage;
}
get imagePullSecret(): string | undefined {
return this.preferences?.imagePullSecret;
}
isInLocalKubeconfig() {
return this.kubeConfigPath.startsWith(this.dependencies.directoryForKubeConfigs);
}
} }

View File

@ -1,13 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectionToken } from "@ogre-tools/injectable";
import type { ClusterConfigData, ClusterModel } from "../cluster-types";
import type { Cluster } from "./cluster";
export type CreateCluster = (model: ClusterModel, configData: ClusterConfigData) => Cluster;
export const createClusterInjectionToken = getInjectionToken<CreateCluster>({
id: "create-cluster-token",
});

View File

@ -5,25 +5,25 @@
import type { KubeConfig } from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node";
import { CoreV1Api } from "@kubernetes/client-node"; import { CoreV1Api } from "@kubernetes/client-node";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { isDefined } from "../utils"; import { isDefined } from "@k8slens/utilities";
export type ListNamespaces = () => Promise<string[]>; export type ListNamespaces = () => Promise<string[]>;
export function listNamespaces(config: KubeConfig): ListNamespaces { export type CreateListNamespaces = (config: KubeConfig) => ListNamespaces;
const coreApi = config.makeApiClient(CoreV1Api);
return async () => { const createListNamespacesInjectable = getInjectable({
const { body: { items }} = await coreApi.listNamespace(); id: "create-list-namespaces",
instantiate: (): CreateListNamespaces => (config) => {
const coreApi = config.makeApiClient(CoreV1Api);
return items return async () => {
.map(ns => ns.metadata?.name) const { body: { items }} = await coreApi.listNamespace();
.filter(isDefined);
};
}
const listNamespacesInjectable = getInjectable({ return items
id: "list-namespaces", .map(ns => ns.metadata?.name)
instantiate: () => listNamespaces, .filter(isDefined);
};
},
}); });
export default listNamespacesInjectable; export default createListNamespacesInjectable;

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { KubeConfig } from "@kubernetes/client-node";
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
import type { Cluster } from "./cluster";
import loadConfigFromFileInjectable from "../kube-helpers/load-config-from-file.injectable";
import type { ConfigResult } from "../kube-helpers";
export interface LoadKubeconfig {
(fullResult?: false): Promise<KubeConfig>;
(fullResult: true): Promise<ConfigResult>;
}
const loadKubeconfigInjectable = getInjectable({
id: "load-kubeconfig",
instantiate: (di, cluster) => {
const loadConfigFromFile = di.inject(loadConfigFromFileInjectable);
return (async (fullResult = false) => {
const result = await loadConfigFromFile(cluster.kubeConfigPath.get());
if (fullResult) {
return result;
}
return result.config;
}) as LoadKubeconfig;
},
lifecycle: lifecycleEnum.keyedSingleton({
getInstanceKey: (di, cluster: Cluster) => cluster.id,
}),
});
export default loadKubeconfigInjectable;

View File

@ -47,9 +47,9 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
const { resourceRules } = status; const { resourceRules } = status;
return (resource) => { return (resource) => {
const resourceRule = resourceRules.find(({ const rules = resourceRules.filter(({
apiGroups = [], apiGroups = ["*"],
resources = [], resources = ["*"],
}) => { }) => {
const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group); const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group);
const isAboutResource = resources.includes("*") || resources.includes(resource.apiName); const isAboutResource = resources.includes("*") || resources.includes(resource.apiName);
@ -57,13 +57,7 @@ const requestNamespaceListPermissionsForInjectable = getInjectable({
return isAboutRelevantApiGroup && isAboutResource; return isAboutRelevantApiGroup && isAboutResource;
}); });
if (!resourceRule) { return rules.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"));
return false;
}
const { verbs } = resourceRule;
return verbs.includes("*") || verbs.includes("list");
}; };
} catch (error) { } catch (error) {
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error }); logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });

View File

@ -0,0 +1,336 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
import type { DiContainer } from "@ogre-tools/injectable";
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
import type { RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
import requestNamespaceListPermissionsForInjectable from "./request-namespace-list-permissions.injectable";
const createStubProxyConfig = (statusResponse: Promise<{ body: { status: V1SubjectRulesReviewStatus }}>) => ({
makeApiClient: () => ({
createSelfSubjectRulesReview: (): Promise<{ body: { status: V1SubjectRulesReviewStatus }}> => statusResponse,
}),
});
describe("requestNamespaceListPermissions", () => {
let di: DiContainer;
let requestNamespaceListPermissions: RequestNamespaceListPermissionsFor;
beforeEach(() => {
di = getDiForUnitTesting();
requestNamespaceListPermissions = di.inject(requestNamespaceListPermissionsForInjectable);
});
describe("when api returns incomplete data", () => {
it("returns truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: true,
resourceRules: [],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when api rejects", () => {
it("returns truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve, reject) => reject("unknown error")),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has all permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["*"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has list permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["list"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when first resourceRule has list permissions for asked resource", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
},
{
apiGroups: ["*"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has all permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: ["*"],
verbs: ["*"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has list permissions for everything", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: ["*"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when last resourceRule has list permissions for asked resource", () => {
it("return truthy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: ["*"],
verbs: ["get"],
},
{
apiGroups: [""],
resources: ["pods"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeTruthy();
});
});
describe("when resourceRules has matching resource without list verb", () => {
it("return falsy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["pods"],
verbs: ["get"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeFalsy();
});
});
describe("when resourceRules has no matching resource with list verb", () => {
it("return falsy function", async () => {
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
new Promise((resolve) => resolve({
body: {
status: {
incomplete: false,
resourceRules: [
{
apiGroups: [""],
resources: ["services"],
verbs: ["list"],
},
],
nonResourceRules: [],
},
},
})),
) as any);
const permissionCheck = await requestPermissions("irrelevant-namespace");
expect(permissionCheck({
apiName: "pods",
group: "",
kind: "Pod",
namespaced: true,
})).toBeFalsy();
});
});
});

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import initializeSentryReportingWithInjectable from "./initialize-sentry-reporting.injectable"; import initializeSentryReportingWithInjectable from "./initialize-sentry-reporting.injectable";
export default getGlobalOverride(initializeSentryReportingWithInjectable, () => () => {}); export default getGlobalOverride(initializeSentryReportingWithInjectable, () => () => {});

View File

@ -4,14 +4,14 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { RequestInit, Response } from "@k8slens/node-fetch"; import type { RequestInit, Response } from "@k8slens/node-fetch";
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import fetchInjectable from "./fetch.injectable"; import fetchInjectable from "./fetch.injectable";
export interface DownloadBinaryOptions { export interface DownloadBinaryOptions {
signal?: AbortSignal | null | undefined; signal?: AbortSignal | null | undefined;
} }
export type DownloadBinary = (url: string, opts?: DownloadBinaryOptions) => Promise<AsyncResult<Buffer, string>>; export type DownloadBinary = (url: string, opts?: DownloadBinaryOptions) => AsyncResult<Buffer, string>;
const downloadBinaryInjectable = getInjectable({ const downloadBinaryInjectable = getInjectable({
id: "download-binary", id: "download-binary",

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { AsyncResult } from "../../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import type { Fetch } from "../fetch.injectable"; import type { Fetch } from "../fetch.injectable";
import type { RequestInit, Response } from "@k8slens/node-fetch"; import type { RequestInit, Response } from "@k8slens/node-fetch";
@ -10,7 +10,7 @@ export interface DownloadJsonOptions {
signal?: AbortSignal | null | undefined; signal?: AbortSignal | null | undefined;
} }
export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => Promise<AsyncResult<unknown, string>>; export type DownloadJson = (url: string, opts?: DownloadJsonOptions) => AsyncResult<unknown, string>;
export const downloadJsonWith = (fetch: Fetch): DownloadJson => async (url, opts) => { export const downloadJsonWith = (fetch: Fetch): DownloadJson => async (url, opts) => {
let result: Response; let result: Response;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function"; import { getGlobalOverrideForFunction } from "@k8slens/test-utils";
import fetchInjectable from "./fetch.injectable"; import fetchInjectable from "./fetch.injectable";
export default getGlobalOverrideForFunction(fetchInjectable); export default getGlobalOverrideForFunction(fetchInjectable);

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function"; import { getGlobalOverrideForFunction } from "@k8slens/test-utils";
import lensFetchInjectable from "./lens-fetch.injectable"; import lensFetchInjectable from "./lens-fetch.injectable";
export default getGlobalOverrideForFunction(lensFetchInjectable); export default getGlobalOverrideForFunction(lensFetchInjectable);

View File

@ -4,7 +4,7 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token";
import { computedOr } from "../../../../../utils/computed-or"; import { computedOr } from "@k8slens/utilities";
import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token";
const ingressesRouteInjectable = getInjectable({ const ingressesRouteInjectable = getInjectable({

View File

@ -12,7 +12,7 @@ import { pipeline } from "@ogre-tools/fp";
describe("verify-that-all-routes-have-component", () => { describe("verify-that-all-routes-have-component", () => {
it("verify that routes have route component", () => { it("verify that routes have route component", () => {
const rendererDi = getDiForUnitTesting({ doGeneralOverrides: true }); const rendererDi = getDiForUnitTesting();
rendererDi.override(clusterStoreInjectable, () => ({ rendererDi.override(clusterStoreInjectable, () => ({
getById: () => null, getById: () => null,

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import copyInjectable from "./copy.injectable"; import copyInjectable from "./copy.injectable";
export default getGlobalOverride(copyInjectable, () => async () => { export default getGlobalOverride(copyInjectable, () => async () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function"; import { getGlobalOverrideForFunction } from "@k8slens/test-utils";
import execFileInjectable from "./exec-file.injectable"; import execFileInjectable from "./exec-file.injectable";
export default getGlobalOverrideForFunction(execFileInjectable); export default getGlobalOverrideForFunction(execFileInjectable);

View File

@ -5,14 +5,14 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { ExecFileException, ExecFileOptions } from "child_process"; import type { ExecFileException, ExecFileOptions } from "child_process";
import { execFile } from "child_process"; import { execFile } from "child_process";
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
export type ExecFileError = ExecFileException & { stderr: string }; export type ExecFileError = ExecFileException & { stderr: string };
export interface ExecFile { export interface ExecFile {
(filePath: string): Promise<AsyncResult<string, ExecFileError>>; (filePath: string): AsyncResult<string, ExecFileError>;
(filePath: string, argsOrOptions: string[] | ExecFileOptions): Promise<AsyncResult<string, ExecFileError>>; (filePath: string, argsOrOptions: string[] | ExecFileOptions): AsyncResult<string, ExecFileError>;
(filePath: string, args: string[], options: ExecFileOptions): Promise<AsyncResult<string, ExecFileError>>; (filePath: string, args: string[], options: ExecFileOptions): AsyncResult<string, ExecFileError>;
} }
const execFileInjectable = getInjectable({ const execFileInjectable = getInjectable({

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import extractTarInjectable from "./extract-tar.injectable"; import extractTarInjectable from "./extract-tar.injectable";
export default getGlobalOverride(extractTarInjectable, () => async () => { export default getGlobalOverride(extractTarInjectable, () => async () => {

View File

@ -7,7 +7,7 @@ import type { ReadOptions } from "fs-extra";
import fse from "fs-extra"; import fse from "fs-extra";
/** /**
* NOTE: Add corrisponding a corrisponding override of this injecable in `src/test-utils/override-fs-with-fakes.ts` * NOTE: Add corresponding override of this injectable in `src/test-utils/override-fs-with-fakes.ts`
*/ */
const fsInjectable = getInjectable({ const fsInjectable = getInjectable({
id: "fs", id: "fs",
@ -21,6 +21,7 @@ const fsInjectable = getInjectable({
rm, rm,
access, access,
stat, stat,
unlink,
}, },
ensureDir, ensureDir,
ensureDirSync, ensureDirSync,
@ -56,6 +57,7 @@ const fsInjectable = getInjectable({
ensureDirSync, ensureDirSync,
createReadStream, createReadStream,
stat, stat,
unlink,
}; };
}, },
causesSideEffects: true, causesSideEffects: true,

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import lstatInjectable from "./lstat.injectable"; import lstatInjectable from "./lstat.injectable";
export default getGlobalOverride(lstatInjectable, () => async () => { export default getGlobalOverride(lstatInjectable, () => async () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import readDirectoryInjectable from "./read-directory.injectable"; import readDirectoryInjectable from "./read-directory.injectable";
export default getGlobalOverride(readDirectoryInjectable, () => async () => { export default getGlobalOverride(readDirectoryInjectable, () => async () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import removePathInjectable from "./remove.injectable"; import removePathInjectable from "./remove.injectable";
export default getGlobalOverride(removePathInjectable, () => async () => { export default getGlobalOverride(removePathInjectable, () => async () => {

View File

@ -0,0 +1,15 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { getInjectable } from "@ogre-tools/injectable";
import fsInjectable from "./fs.injectable";
export type Unlink = (path: string) => Promise<void>;
const unlinkInjectable = getInjectable({
id: "unlink",
instantiate: (di): Unlink => di.inject(fsInjectable).unlink,
});
export default unlinkInjectable;

View File

@ -3,13 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import { isErrnoException } from "../utils"; import { isErrnoException } from "@k8slens/utilities";
import type { Stats } from "fs-extra"; import type { Stats } from "fs-extra";
import { lowerFirst } from "lodash/fp"; import { lowerFirst } from "lodash/fp";
import statInjectable from "./stat.injectable"; import statInjectable from "./stat.injectable";
export type ValidateDirectory = (path: string) => Promise<AsyncResult<undefined>>; export type ValidateDirectory = (path: string) => AsyncResult<undefined>;
function getUserReadableFileType(stats: Stats): string { function getUserReadableFileType(stats: Stats): string {
if (stats.isFile()) { if (stats.isFile()) {

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import watchInjectable from "./watch.injectable"; import watchInjectable from "./watch.injectable";
export default getGlobalOverride(watchInjectable, () => () => { export default getGlobalOverride(watchInjectable, () => () => {

View File

@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable";
import { watch } from "chokidar"; import { watch } from "chokidar";
import type { Stats } from "fs"; import type { Stats } from "fs";
import type TypedEventEmitter from "typed-emitter"; import type TypedEventEmitter from "typed-emitter";
import type { SingleOrMany } from "../../utils"; import type { SingleOrMany } from "@k8slens/utilities";
export interface AlwaysStatWatcherEvents { export interface AlwaysStatWatcherEvents {
add: (path: string, stats: Stats) => void; add: (path: string, stats: Stats) => void;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import writeFileInjectable from "./write-file.injectable"; import writeFileInjectable from "./write-file.injectable";
export default getGlobalOverride(writeFileInjectable, () => async () => { export default getGlobalOverride(writeFileInjectable, () => async () => {

View File

@ -5,7 +5,7 @@
import assert from "assert"; import assert from "assert";
import path from "path"; import path from "path";
import { getGlobalOverride } from "../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import getConfigurationFileModelInjectable from "./get-configuration-file-model.injectable"; import getConfigurationFileModelInjectable from "./get-configuration-file-model.injectable";
import type Config from "conf"; import type Config from "conf";
import readJsonSyncInjectable from "../fs/read-json-sync.injectable"; import readJsonSyncInjectable from "../fs/read-json-sync.injectable";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { HelmRepo } from "./helm-repo"; import type { HelmRepo } from "./helm-repo";
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>; export type AddHelmRepositoryChannel = RequestChannel<HelmRepo, AsyncResult<void, string>>;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { HelmRepo } from "./helm-repo"; import type { HelmRepo } from "./helm-repo";
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
export type GetActiveHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>; export type GetActiveHelmRepositoriesChannel = RequestChannel<void, AsyncResult<HelmRepo[]>>;

View File

@ -2,7 +2,7 @@
* Copyright (c) OpenLens Authors. All rights reserved. * Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { AsyncResult } from "../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token"; import type { RequestChannel } from "../utils/channel/request-channel-listener-injection-token";
import type { HelmRepo } from "./helm-repo"; import type { HelmRepo } from "./helm-repo";

View File

@ -4,8 +4,8 @@
*/ */
import * as uuid from "uuid"; import * as uuid from "uuid";
import type { Tuple } from "../utils"; import type { Tuple } from "@k8slens/utilities";
import { tuple } from "../utils"; import { tuple } from "@k8slens/utilities";
export interface HotbarItem { export interface HotbarItem {
entity: { entity: {

View File

@ -15,7 +15,7 @@ describe("InitializableState tests", () => {
let di: DiContainer; let di: DiContainer;
beforeEach(() => { beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
}); });
describe("when created", () => { describe("when created", () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverrideForFunction } from "../test-utils/get-global-override-for-function"; import { getGlobalOverrideForFunction } from "@k8slens/test-utils";
import broadcastMessageInjectable from "./broadcast-message.injectable"; import broadcastMessageInjectable from "./broadcast-message.injectable";
export default getGlobalOverrideForFunction(broadcastMessageInjectable); export default getGlobalOverrideForFunction(broadcastMessageInjectable);

View File

@ -11,7 +11,7 @@ import { ipcMain, ipcRenderer, webContents } from "electron";
import { toJS } from "../utils/toJS"; import { toJS } from "../utils/toJS";
import type { ClusterFrameInfo } from "../cluster-frames"; import type { ClusterFrameInfo } from "../cluster-frames";
import { clusterFrameMap } from "../cluster-frames"; import { clusterFrameMap } from "../cluster-frames";
import type { Disposer } from "../utils"; import type { Disposer } from "@k8slens/utilities";
import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import { getLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable"; import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable";
import loggerInjectable from "../logger.injectable"; import loggerInjectable from "../logger.injectable";

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import autoBind from "auto-bind";
import orderBy from "lodash/orderBy"; import orderBy from "lodash/orderBy";
import { autoBind } from "./utils";
import { action, computed, observable, when, makeObservable } from "mobx"; import { action, computed, observable, when, makeObservable } from "mobx";
export interface ItemObject { export interface ItemObject {

View File

@ -4,7 +4,6 @@
*/ */
import type { DiContainer } from "@ogre-tools/injectable"; import type { DiContainer } from "@ogre-tools/injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable"; import clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting"; import { getDiForUnitTesting } from "../../../renderer/getDiForUnitTesting";
@ -19,6 +18,10 @@ import { KubeObject } from "../kube-object";
import { KubeObjectStore } from "../kube-object.store"; import { KubeObjectStore } from "../kube-object.store";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
// eslint-disable-next-line no-restricted-imports
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
import { Cluster } from "../../cluster/cluster";
class TestApi extends KubeApi<KubeObject> { class TestApi extends KubeApi<KubeObject> {
protected async checkPreferredVersion() { protected async checkPreferredVersion() {
return; return;
@ -34,15 +37,13 @@ describe("ApiManager", () => {
let di: DiContainer; let di: DiContainer;
beforeEach(() => { beforeEach(() => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable); di.override(hostedClusterInjectable, () => new Cluster({
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name", contextName: "some-context-name",
id: "some-cluster-id", id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig", kubeConfigPath: "/some-path-to-a-kubeconfig",
@ -54,7 +55,7 @@ describe("ApiManager", () => {
}); });
describe("registerApi", () => { describe("registerApi", () => {
it("re-register store if apiBase changed", async () => { it("re-register store if apiBase changed", () => {
const apiBase = "apis/v1/foo"; const apiBase = "apis/v1/foo";
const fallbackApiBase = "/apis/extensions/v1beta1/foo"; const fallbackApiBase = "/apis/extensions/v1beta1/foo";
const kubeApi = new TestApi({ const kubeApi = new TestApi({
@ -72,21 +73,48 @@ describe("ApiManager", () => {
logger: di.inject(loggerInjectable), logger: di.inject(loggerInjectable),
}, kubeApi); }, kubeApi);
apiManager.registerApi(apiBase, kubeApi); apiManager.registerApi(kubeApi);
// Define to use test api for ingress store // Define to use test api for ingress store
Object.defineProperty(kubeStore, "api", { value: kubeApi }); Object.defineProperty(kubeStore, "api", { value: kubeApi });
apiManager.registerStore(kubeStore, [kubeApi]); apiManager.registerStore(kubeStore);
// Test that store is returned with original apiBase // Test that store is returned with original apiBase
expect(apiManager.getStore(kubeApi)).toBe(kubeStore); expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
// Change apiBase similar as checkPreferredVersion does // Change apiBase similar as checkPreferredVersion does
Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase }); Object.defineProperty(kubeApi, "apiBase", { value: fallbackApiBase });
apiManager.registerApi(fallbackApiBase, kubeApi); apiManager.registerApi(kubeApi);
// Test that store is returned with new apiBase // Test that store is returned with new apiBase
expect(apiManager.getStore(kubeApi)).toBe(kubeStore); expect(apiManager.getStore(kubeApi)).toBe(kubeStore);
}); });
}); });
describe("technical tests for autorun", () => {
it("given two extensions register apis with the same apibase, settle into using the second", () => {
const apiBase = "/apis/aquasecurity.github.io/v1alpha1/vulnerabilityreports";
const firstApi = Object.assign(new ExternalKubeApi({
objectConstructor: KubeObject,
apiBase,
kind: "VulnerabilityReport",
}), {
myField: 1,
});
const secondApi = Object.assign(new ExternalKubeApi({
objectConstructor: KubeObject,
apiBase,
kind: "VulnerabilityReport",
}), {
myField: 2,
});
void firstApi;
void secondApi;
expect(apiManager.getApi(apiBase)).toMatchObject({
myField: 2,
});
});
});
}); });

View File

@ -15,7 +15,7 @@ describe("DeploymentApi", () => {
let kubeJsonApi: jest.Mocked<KubeJsonApi>; let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting();
di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = { kubeJsonApi = {

View File

@ -10,12 +10,11 @@ import type { Fetch } from "../../fetch/fetch.injectable";
import fetchInjectable from "../../fetch/fetch.injectable"; import fetchInjectable from "../../fetch/fetch.injectable";
import type { AsyncFnMock } from "@async-fn/jest"; import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../test-utils/flush-promises"; import { flushPromises } from "@k8slens/test-utils";
import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable"; import setupAutoRegistrationInjectable from "../../../renderer/before-frame-starts/runnables/setup-auto-registration.injectable";
import { createMockResponseFromString } from "../../../test-utils/mock-responses"; import { createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiManagerInjectable from "../api-manager/manager.injectable"; import apiManagerInjectable from "../api-manager/manager.injectable";
@ -23,6 +22,7 @@ import type { DiContainer } from "@ogre-tools/injectable";
import ingressApiInjectable from "../endpoints/ingress.api.injectable"; import ingressApiInjectable from "../endpoints/ingress.api.injectable";
import loggerInjectable from "../../logger.injectable"; import loggerInjectable from "../../logger.injectable";
import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
import { Cluster } from "../../cluster/cluster";
describe("KubeApi", () => { describe("KubeApi", () => {
let fetchMock: AsyncFnMock<Fetch>; let fetchMock: AsyncFnMock<Fetch>;
@ -30,7 +30,7 @@ describe("KubeApi", () => {
let di: DiContainer; let di: DiContainer;
beforeEach(async () => { beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
fetchMock = asyncFn(); fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock); di.override(fetchInjectable, () => fetchMock);
@ -39,9 +39,7 @@ describe("KubeApi", () => {
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable); di.override(hostedClusterInjectable, () => new Cluster({
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name", contextName: "some-context-name",
id: "some-cluster-id", id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig", kubeConfigPath: "/some-path-to-a-kubeconfig",
@ -121,7 +119,7 @@ describe("KubeApi", () => {
]); ]);
}); });
describe("when resource request fufills with a resource", () => { describe("when resource request fulfills with a resource", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1"], ["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1"],
@ -283,7 +281,7 @@ describe("KubeApi", () => {
}); });
}); });
describe("when resource request fufills with no resource", () => { describe("when resource request fulfills with no resource", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1"], ["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1"],
@ -307,7 +305,7 @@ describe("KubeApi", () => {
describe("when resource request fufills with a resource", () => { describe("when resource request fulfills with a resource", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1beta1"], ["https://127.0.0.1:12345/api-kube/apis/networking.k8s.io/v1beta1"],
@ -509,7 +507,7 @@ describe("KubeApi", () => {
]); ]);
}); });
describe("when resource request fufills with a resource", () => { describe("when resource request fulfills with a resource", () => {
beforeEach(async () => { beforeEach(async () => {
await fetchMock.resolveSpecific( await fetchMock.resolveSpecific(
["https://127.0.0.1:12345/api-kube/apis/extensions"], ["https://127.0.0.1:12345/api-kube/apis/extensions"],

View File

@ -15,7 +15,7 @@ import type { CreateKubeApiForRemoteCluster } from "../create-kube-api-for-remot
import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable"; import createKubeApiForRemoteClusterInjectable from "../create-kube-api-for-remote-cluster.injectable";
import type { AsyncFnMock } from "@async-fn/jest"; import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import { flushPromises } from "../../test-utils/flush-promises"; import { flushPromises } from "@k8slens/test-utils";
import createKubeJsonApiInjectable from "../create-kube-json-api.injectable"; import createKubeJsonApiInjectable from "../create-kube-json-api.injectable";
import type { IKubeWatchEvent } from "../kube-watch-event"; import type { IKubeWatchEvent } from "../kube-watch-event";
import type { KubeJsonApiDataFor } from "../kube-object"; import type { KubeJsonApiDataFor } from "../kube-object";
@ -24,7 +24,6 @@ import setupAutoRegistrationInjectable from "../../../renderer/before-frame-star
import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses"; import { createMockResponseFromStream, createMockResponseFromString } from "../../../test-utils/mock-responses";
import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable"; import storesAndApisCanBeCreatedInjectable from "../../../renderer/stores-apis-can-be-created.injectable";
import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForUserDataInjectable from "../../app-paths/directory-for-user-data/directory-for-user-data.injectable";
import createClusterInjectable from "../../../main/create-cluster/create-cluster.injectable";
import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable"; import hostedClusterInjectable from "../../../renderer/cluster-frame-context/hosted-cluster.injectable";
import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import directoryForKubeConfigsInjectable from "../../app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable"; import apiKubeInjectable from "../../../renderer/k8s/api-kube.injectable";
@ -36,21 +35,20 @@ import namespaceApiInjectable from "../endpoints/namespace.api.injectable";
// NOTE: this is fine because we are testing something that only exported // NOTE: this is fine because we are testing something that only exported
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { PodsApi } from "../../../extensions/common-api/k8s-api"; import { PodsApi } from "../../../extensions/common-api/k8s-api";
import { Cluster } from "../../cluster/cluster";
describe("createKubeApiForRemoteCluster", () => { describe("createKubeApiForRemoteCluster", () => {
let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster; let createKubeApiForRemoteCluster: CreateKubeApiForRemoteCluster;
let fetchMock: AsyncFnMock<Fetch>; let fetchMock: AsyncFnMock<Fetch>;
beforeEach(async () => { beforeEach(async () => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(storesAndApisCanBeCreatedInjectable, () => true);
const createCluster = di.inject(createClusterInjectable); di.override(hostedClusterInjectable, () => new Cluster({
di.override(hostedClusterInjectable, () => createCluster({
contextName: "some-context-name", contextName: "some-context-name",
id: "some-cluster-id", id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig", kubeConfigPath: "/some-path-to-a-kubeconfig",
@ -145,7 +143,7 @@ describe("KubeApi", () => {
let di: DiContainer; let di: DiContainer;
beforeEach(async () => { beforeEach(async () => {
di = getDiForUnitTesting({ doGeneralOverrides: true }); di = getDiForUnitTesting();
di.override(directoryForUserDataInjectable, () => "/some-user-store-path"); di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs"); di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
@ -154,10 +152,9 @@ describe("KubeApi", () => {
fetchMock = asyncFn(); fetchMock = asyncFn();
di.override(fetchInjectable, () => fetchMock); di.override(fetchInjectable, () => fetchMock);
const createCluster = di.inject(createClusterInjectable);
const createKubeJsonApi = di.inject(createKubeJsonApiInjectable); const createKubeJsonApi = di.inject(createKubeJsonApiInjectable);
di.override(hostedClusterInjectable, () => createCluster({ di.override(hostedClusterInjectable, () => new Cluster({
contextName: "some-context-name", contextName: "some-context-name",
id: "some-cluster-id", id: "some-cluster-id",
kubeConfigPath: "/some-path-to-a-kubeconfig", kubeConfigPath: "/some-path-to-a-kubeconfig",

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { noop } from "../../utils"; import { noop } from "@k8slens/utilities";
import type { KubeApi } from "../kube-api"; import type { KubeApi } from "../kube-api";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import type { KubeObjectStoreLoadingParams } from "../kube-object.store"; import type { KubeObjectStoreLoadingParams } from "../kube-object.store";

View File

@ -15,7 +15,7 @@ describe("StatefulSetApi", () => {
let kubeJsonApi: jest.Mocked<KubeJsonApi>; let kubeJsonApi: jest.Mocked<KubeJsonApi>;
beforeEach(() => { beforeEach(() => {
const di = getDiForUnitTesting({ doGeneralOverrides: true }); const di = getDiForUnitTesting();
di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(storesAndApisCanBeCreatedInjectable, () => true);
kubeJsonApi = { kubeJsonApi = {

View File

@ -10,7 +10,7 @@ import { autorun, action, observable } from "mobx";
import type { KubeApi } from "../kube-api"; import type { KubeApi } from "../kube-api";
import type { KubeObject, ObjectReference } from "../kube-object"; import type { KubeObject, ObjectReference } from "../kube-object";
import { parseKubeApi, createKubeApiURL } from "../kube-api-parse"; import { parseKubeApi, createKubeApiURL } from "../kube-api-parse";
import { chain, find } from "../../utils/iter"; import { iter } from "@k8slens/utilities";
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any> export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
? Store ? Store
@ -38,28 +38,31 @@ export class ApiManager {
constructor(private readonly dependencies: Dependencies) { constructor(private readonly dependencies: Dependencies) {
// NOTE: this is done to preserve the old behaviour of an API being discoverable using all previous apiBases // NOTE: this is done to preserve the old behaviour of an API being discoverable using all previous apiBases
autorun(() => { autorun(() => {
const apis = chain(this.dependencies.apis.get().values()) const apis = iter.chain(this.dependencies.apis.get().values())
.concat(this.externalApis.values()); .concat(this.externalApis.values());
const removedApis = new Set(this.apis.values()); const removedApis = new Set(this.apis.values());
const newState = new Map(this.apis);
for (const api of apis) { for (const api of apis) {
removedApis.delete(api); removedApis.delete(api);
this.apis.set(api.apiBase, api); newState.set(api.apiBase, api);
} }
for (const api of removedApis) { for (const api of removedApis) {
for (const [apiBase, storedApi] of this.apis) { for (const [apiBase, storedApi] of newState) {
if (storedApi === api) { if (storedApi === api) {
this.apis.delete(apiBase); newState.delete(apiBase);
} }
} }
} }
this.apis.replace(newState);
}); });
} }
getApi(pathOrCallback: string | FindApiCallback) { getApi(pathOrCallback: string | FindApiCallback) {
if (typeof pathOrCallback === "function") { if (typeof pathOrCallback === "function") {
return find(this.apis.values(), pathOrCallback); return iter.find(this.apis.values(), pathOrCallback);
} }
const { apiBase } = parseKubeApi(pathOrCallback); const { apiBase } = parseKubeApi(pathOrCallback);
@ -127,7 +130,7 @@ export class ApiManager {
return undefined; return undefined;
} }
return chain(this.dependencies.stores.get().values()) return iter.chain(this.dependencies.stores.get().values())
.concat(this.externalStores.values()) .concat(this.externalStores.values())
.find(store => store.api.apiBase === api.apiBase); .find(store => store.api.apiBase === api.apiBase);
} }

View File

@ -8,7 +8,7 @@ import { KubeObject } from "../kube-object";
import type { KubeJsonApiData } from "../kube-json-api"; import type { KubeJsonApiData } from "../kube-json-api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { autoBind } from "../../utils"; import autoBind from "auto-bind";
export interface ConfigMapData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> { export interface ConfigMapData extends KubeJsonApiData<KubeObjectMetadata<KubeObjectScope.Namespace>, void, void> {
data?: Partial<Record<string, string>>; data?: Partial<Record<string, string>>;

View File

@ -6,7 +6,7 @@
import moment from "moment"; import moment from "moment";
import type { NamespaceScopedMetadata, ObjectReference } from "../kube-object"; import type { NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { formatDuration } from "../../utils/formatDuration"; import { formatDuration } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { JobTemplateSpec } from "./types/job-template-spec"; import type { JobTemplateSpec } from "./types/job-template-spec";

View File

@ -5,7 +5,7 @@
import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api";
import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable"; import customResourcesRouteInjectable from "../../front-end-routing/routes/cluster/custom-resources/custom-resources/custom-resources-route.injectable";
import { buildURL } from "../../utils/buildUrl"; import { buildURL } from "@k8slens/utilities";
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object"; import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";

View File

@ -10,7 +10,7 @@ import { KubeApi } from "../kube-api";
import type { PodSpec } from "./pod.api"; import type { PodSpec } from "./pod.api";
import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import type { KubeObjectStatus, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { hasTypedProperty, isNumber, isObject } from "../../utils"; import { hasTypedProperty, isNumber, isObject } from "@k8slens/utilities";
export class DeploymentApi extends KubeApi<Deployment> { export class DeploymentApi extends KubeApi<Deployment> {
constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { autoBind } from "../../utils"; import autoBind from "auto-bind";
import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata, ObjectReference } from "../kube-object"; import type { KubeObjectMetadata, KubeObjectScope, NamespaceScopedMetadata, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";

View File

@ -6,7 +6,7 @@
import moment from "moment"; import moment from "moment";
import type { KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../kube-object"; import type { KubeObjectMetadata, KubeObjectScope, ObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { formatDuration } from "../../utils/formatDuration"; import { formatDuration } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { KubeJsonApiData } from "../kube-json-api"; import type { KubeJsonApiData } from "../kube-json-api";

View File

@ -3,7 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { autoBind, bifurcateArray } from "../../utils"; import { array } from "@k8slens/utilities";
import autoBind from "auto-bind";
import Joi from "joi"; import Joi from "joi";
export interface RawHelmChart { export interface RawHelmChart {
@ -263,7 +264,7 @@ export class HelmChart implements HelmChartData {
return new HelmChart(result.value); return new HelmChart(result.value);
} }
const [actualErrors, unknownDetails] = bifurcateArray(result.error.details, ({ type }) => type === "object.unknown"); const [actualErrors, unknownDetails] = array.bifurcate(result.error.details, ({ type }) => type === "object.unknown");
if (unknownDetails.length > 0) { if (unknownDetails.length > 0) {
console.warn("HelmChart data has unexpected fields", { original: data, unknownFields: unknownDetails.flatMap(d => d.path) }); console.warn("HelmChart data has unexpected fields", { original: data, unknownFields: unknownDetails.flatMap(d => d.path) });

View File

@ -5,7 +5,7 @@
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { RawHelmChart } from "../helm-charts.api"; import type { RawHelmChart } from "../helm-charts.api";
import { HelmChart } from "../helm-charts.api"; import { HelmChart } from "../helm-charts.api";
import { isDefined } from "../../../utils"; import { isDefined } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export type RequestHelmCharts = () => Promise<HelmChart[]>; export type RequestHelmCharts = () => Promise<HelmChart[]>;

View File

@ -3,13 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncResult } from "../../../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
const requestReadmeEndpoint = urlBuilderFor("/v2/charts/:repo/:name/readme"); const requestReadmeEndpoint = urlBuilderFor("/v2/charts/:repo/:name/readme");
export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => Promise<AsyncResult<string>>; export type RequestHelmChartReadme = (repo: string, name: string, version?: string) => AsyncResult<string>;
const requestHelmChartReadmeInjectable = getInjectable({ const requestHelmChartReadmeInjectable = getInjectable({
id: "request-helm-chart-readme", id: "request-helm-chart-readme",

View File

@ -3,13 +3,13 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { AsyncResult } from "../../../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
const requestValuesEndpoint = urlBuilderFor("/v2/charts/:repo/:name/values"); const requestValuesEndpoint = urlBuilderFor("/v2/charts/:repo/:name/values");
export type RequestHelmChartValues = (repo: string, name: string, version: string) => Promise<AsyncResult<string>>; export type RequestHelmChartValues = (repo: string, name: string, version: string) => AsyncResult<string>;
const requestHelmChartValuesInjectable = getInjectable({ const requestHelmChartValuesInjectable = getInjectable({
id: "request-helm-chart-values", id: "request-helm-chart-values",

View File

@ -3,10 +3,9 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor, isDefined } from "@k8slens/utilities";
import { HelmChart } from "../helm-charts.api"; import { HelmChart } from "../helm-charts.api";
import type { RawHelmChart } from "../helm-charts.api"; import type { RawHelmChart } from "../helm-charts.api";
import { isDefined } from "../../../utils";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
const requestVersionsEndpoint = urlBuilderFor("/v2/charts/:repo/:name/versions"); const requestVersionsEndpoint = urlBuilderFor("/v2/charts/:repo/:name/versions");

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getGlobalOverride } from "../../../test-utils/get-global-override"; import { getGlobalOverride } from "@k8slens/test-utils";
import requestHelmReleaseConfigurationInjectable from "./request-configuration.injectable"; import requestHelmReleaseConfigurationInjectable from "./request-configuration.injectable";
export default getGlobalOverride(requestHelmReleaseConfigurationInjectable, () => () => { export default getGlobalOverride(requestHelmReleaseConfigurationInjectable, () => () => {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export type RequestHelmReleaseConfiguration = ( export type RequestHelmReleaseConfiguration = (

View File

@ -5,7 +5,7 @@
import yaml from "js-yaml"; import yaml from "js-yaml";
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { HelmReleaseUpdateDetails } from "../helm-releases.api"; import type { HelmReleaseUpdateDetails } from "../helm-releases.api";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
interface HelmReleaseCreatePayload { interface HelmReleaseCreatePayload {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export type RequestDeleteHelmRelease = (name: string, namespace: string) => Promise<void>; export type RequestDeleteHelmRelease = (name: string, namespace: string) => Promise<void>;

View File

@ -4,7 +4,7 @@
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import type { KubeJsonApiData } from "../../kube-json-api"; import type { KubeJsonApiData } from "../../kube-json-api";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export interface HelmReleaseDetails { export interface HelmReleaseDetails {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export interface HelmReleaseRevision { export interface HelmReleaseRevision {

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
import type { HelmReleaseDto } from "../helm-releases.api"; import type { HelmReleaseDto } from "../helm-releases.api";

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export type RequestHelmReleaseRollback = (name: string, namespace: string, revision: number) => Promise<void>; export type RequestHelmReleaseRollback = (name: string, namespace: string, revision: number) => Promise<void>;

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import type { AsyncResult } from "../../../utils/async-result"; import type { AsyncResult } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
interface HelmReleaseUpdatePayload { interface HelmReleaseUpdatePayload {
@ -18,7 +18,7 @@ export type RequestHelmReleaseUpdate = (
name: string, name: string,
namespace: string, namespace: string,
payload: HelmReleaseUpdatePayload payload: HelmReleaseUpdatePayload
) => Promise<AsyncResult<void, unknown>>; ) => AsyncResult<void, unknown>;
const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name"); const requestUpdateEndpoint = urlBuilderFor("/v2/releases/:namespace/:name");

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { getInjectable } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable";
import { urlBuilderFor } from "../../../utils/buildUrl"; import { urlBuilderFor } from "@k8slens/utilities";
import apiBaseInjectable from "../../api-base.injectable"; import apiBaseInjectable from "../../api-base.injectable";
export type RequestHelmReleaseValues = (name: string, namespace: string, all?: boolean) => Promise<string>; export type RequestHelmReleaseValues = (name: string, namespace: string, all?: boolean) => Promise<string>;

View File

@ -3,7 +3,7 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { OptionVarient } from "../../utils"; import type { OptionVariant } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object"; import type { BaseKubeObjectCondition, LabelSelector, NamespaceScopedMetadata } from "../kube-object";
@ -152,11 +152,11 @@ export interface BaseHorizontalPodAutoscalerMetricSpec {
} }
export type HorizontalPodAutoscalerMetricSpec = export type HorizontalPodAutoscalerMetricSpec =
| OptionVarient<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricSpec, "resource"> | OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricSpec, "resource">
| OptionVarient<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricSpec, "external"> | OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricSpec, "external">
| OptionVarient<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricSpec, "object"> | OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricSpec, "object">
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods"> | OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricSpec, "pods">
| OptionVarient<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">; | OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricSpec, "containerResource">;
interface HorizontalPodAutoscalerBehavior { interface HorizontalPodAutoscalerBehavior {
scaleUp?: HPAScalingRules; scaleUp?: HPAScalingRules;
@ -294,11 +294,11 @@ export interface BaseHorizontalPodAutoscalerMetricStatus {
} }
export type HorizontalPodAutoscalerMetricStatus = export type HorizontalPodAutoscalerMetricStatus =
| OptionVarient<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricStatus, "resource"> | OptionVariant<HpaMetricType.Resource, BaseHorizontalPodAutoscalerMetricStatus, "resource">
| OptionVarient<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricStatus, "external"> | OptionVariant<HpaMetricType.External, BaseHorizontalPodAutoscalerMetricStatus, "external">
| OptionVarient<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricStatus, "object"> | OptionVariant<HpaMetricType.Object, BaseHorizontalPodAutoscalerMetricStatus, "object">
| OptionVarient<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricStatus, "pods"> | OptionVariant<HpaMetricType.Pods, BaseHorizontalPodAutoscalerMetricStatus, "pods">
| OptionVarient<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricStatus, "containerResource">; | OptionVariant<HpaMetricType.ContainerResource, BaseHorizontalPodAutoscalerMetricStatus, "containerResource">;
export interface HorizontalPodAutoscalerSpec { export interface HorizontalPodAutoscalerSpec {
scaleTargetRef: CrossVersionObjectReference; scaleTargetRef: CrossVersionObjectReference;

View File

@ -5,7 +5,7 @@
import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object"; import type { NamespaceScopedMetadata, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { hasTypedProperty, isString, iter } from "../../utils"; import { hasTypedProperty, isString, iter } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { RequireExactlyOne } from "type-fest"; import type { RequireExactlyOne } from "type-fest";

View File

@ -6,7 +6,7 @@
// Metrics api // Metrics api
import moment from "moment"; import moment from "moment";
import { isDefined, object } from "../../utils"; import { isDefined, object } from "@k8slens/utilities";
export interface MetricData { export interface MetricData {
status: string; status: string;

View File

@ -5,7 +5,7 @@
import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object"; import type { BaseKubeObjectCondition, ClusterScopedMetadata } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { cpuUnitsToNumber, unitsToBytes, isObject } from "../../../renderer/utils"; import { cpuUnitsToNumber, unitsToBytes, isObject } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { TypedRegEx } from "typed-regex"; import { TypedRegEx } from "typed-regex";

View File

@ -8,7 +8,7 @@ import { KubeObject } from "../kube-object";
import type { Pod } from "./pod.api"; import type { Pod } from "./pod.api";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import { object } from "../../utils"; import { object } from "@k8slens/utilities";
import type { ResourceRequirements } from "./types/resource-requirements"; import type { ResourceRequirements } from "./types/resource-requirements";
export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> { export class PersistentVolumeClaimApi extends KubeApi<PersistentVolumeClaim> {

View File

@ -5,7 +5,7 @@
import type { ClusterScopedMetadata, LabelSelector, ObjectReference, TypedLocalObjectReference } from "../kube-object"; import type { ClusterScopedMetadata, LabelSelector, ObjectReference, TypedLocalObjectReference } from "../kube-object";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { unitsToBytes } from "../../utils"; import { unitsToBytes } from "@k8slens/utilities";
import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api";
import { KubeApi } from "../kube-api"; import { KubeApi } from "../kube-api";
import type { ResourceRequirements } from "./types/resource-requirements"; import type { ResourceRequirements } from "./types/resource-requirements";

View File

@ -10,7 +10,7 @@ import type { KubeObjectMetadata, LocalObjectReference, Affinity, Toleration, Na
import type { SecretReference } from "./secret.api"; import type { SecretReference } from "./secret.api";
import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api"; import type { PersistentVolumeClaimSpec } from "./persistent-volume-claim.api";
import { KubeObject } from "../kube-object"; import { KubeObject } from "../kube-object";
import { isDefined } from "../../utils"; import { isDefined } from "@k8slens/utilities";
import type { PodSecurityContext } from "./types/pod-security-context"; import type { PodSecurityContext } from "./types/pod-security-context";
import type { Probe } from "./types/probe"; import type { Probe } from "./types/probe";
import type { Container } from "./types/container"; import type { Container } from "./types/container";

Some files were not shown because too many files have changed in this diff Show More