mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Merge branch 'master' into no-updates-available-popup
This commit is contained in:
commit
cf47e8b38c
@ -15,8 +15,8 @@ All releases will be made by creating a PR which bumps the version field in the
|
|||||||
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`.
|
1. Run `npm run create-release-pr`.
|
||||||
1. Pick the PRs that you want to include in this release using the keys listed.
|
1. Pick the PRs that you want to include in this release using the keys listed.
|
||||||
- If you are making a patch release this might include fixing up some cherry-picking of commits. These actions should be done in a separate terminal.
|
- If you are making a patch release this might include fixing up some cherry-picking of commits. These actions should be done in a separate terminal.
|
||||||
- If a package version is having a major version bump then `npm` will complain about `peerDependency` conflicts. These will have to be fixed up separately.
|
- If a package version is having a major version bump then `npm` will complain about `peerDependency` conflicts. These will have to be fixed up separately.
|
||||||
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 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.
|
||||||
|
|||||||
392
package-lock.json
generated
392
package-lock.json
generated
@ -2099,92 +2099,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/get": {
|
"node_modules/@electron/get": {
|
||||||
"version": "1.14.1",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@electron/get/-/get-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.2.tgz",
|
||||||
"integrity": "sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==",
|
"integrity": "sha512-eFZVFoRXb3GFGd7Ak7W4+6jBl9wBtiZ4AaYOse97ej6mKj5tkyO0dUnUChs1IhJZtx1BENo4/p4WUTXpi6vT+g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"env-paths": "^2.2.0",
|
"env-paths": "^2.2.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"got": "^9.6.0",
|
"got": "^11.8.5",
|
||||||
"progress": "^2.0.3",
|
"progress": "^2.0.3",
|
||||||
"semver": "^6.2.0",
|
"semver": "^6.2.0",
|
||||||
"sumchecker": "^3.0.1"
|
"sumchecker": "^3.0.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"global-agent": "^3.0.0",
|
"global-agent": "^3.0.0"
|
||||||
"global-tunnel-ng": "^2.7.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/get/node_modules/@sindresorhus/is": {
|
|
||||||
"version": "0.14.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
|
|
||||||
"integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/@szmarczak/http-timer": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==",
|
|
||||||
"dependencies": {
|
|
||||||
"defer-to-connect": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/cacheable-request": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz",
|
|
||||||
"integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==",
|
|
||||||
"dependencies": {
|
|
||||||
"clone-response": "^1.0.2",
|
|
||||||
"get-stream": "^5.1.0",
|
|
||||||
"http-cache-semantics": "^4.0.0",
|
|
||||||
"keyv": "^3.0.0",
|
|
||||||
"lowercase-keys": "^2.0.0",
|
|
||||||
"normalize-url": "^4.1.0",
|
|
||||||
"responselike": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/cacheable-request/node_modules/get-stream": {
|
|
||||||
"version": "5.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
|
||||||
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
|
||||||
"dependencies": {
|
|
||||||
"pump": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/decompress-response": {
|
|
||||||
"version": "3.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
|
||||||
"integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==",
|
|
||||||
"dependencies": {
|
|
||||||
"mimic-response": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/defer-to-connect": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/fs-extra": {
|
"node_modules/@electron/get/node_modules/fs-extra": {
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
||||||
@ -2198,51 +2131,6 @@
|
|||||||
"node": ">=6 <7 || >=8"
|
"node": ">=6 <7 || >=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/get/node_modules/get-stream": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
|
|
||||||
"dependencies": {
|
|
||||||
"pump": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/got": {
|
|
||||||
"version": "9.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
|
|
||||||
"integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"@sindresorhus/is": "^0.14.0",
|
|
||||||
"@szmarczak/http-timer": "^1.1.2",
|
|
||||||
"cacheable-request": "^6.0.0",
|
|
||||||
"decompress-response": "^3.3.0",
|
|
||||||
"duplexer3": "^0.1.4",
|
|
||||||
"get-stream": "^4.1.0",
|
|
||||||
"lowercase-keys": "^1.0.1",
|
|
||||||
"mimic-response": "^1.0.1",
|
|
||||||
"p-cancelable": "^1.0.0",
|
|
||||||
"to-readable-stream": "^1.0.0",
|
|
||||||
"url-parse-lax": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/got/node_modules/lowercase-keys": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/json-buffer": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ=="
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/jsonfile": {
|
"node_modules/@electron/get/node_modules/jsonfile": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||||
@ -2251,55 +2139,6 @@
|
|||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/get/node_modules/keyv": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==",
|
|
||||||
"dependencies": {
|
|
||||||
"json-buffer": "3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/normalize-url": {
|
|
||||||
"version": "4.5.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz",
|
|
||||||
"integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/p-cancelable": {
|
|
||||||
"version": "1.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz",
|
|
||||||
"integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/pump": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
|
||||||
"dependencies": {
|
|
||||||
"end-of-stream": "^1.1.0",
|
|
||||||
"once": "^1.3.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/responselike": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"lowercase-keys": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/responselike/node_modules/lowercase-keys": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@electron/get/node_modules/semver": {
|
"node_modules/@electron/get/node_modules/semver": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||||
@ -4685,6 +4524,10 @@
|
|||||||
"resolved": "packages/bump-version-for-cron",
|
"resolved": "packages/bump-version-for-cron",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@k8slens/cluster-settings": {
|
||||||
|
"resolved": "packages/cluster-settings",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@k8slens/computed-channel": {
|
"node_modules/@k8slens/computed-channel": {
|
||||||
"resolved": "packages/technical-features/messaging/computed-channel",
|
"resolved": "packages/technical-features/messaging/computed-channel",
|
||||||
"link": true
|
"link": true
|
||||||
@ -7977,6 +7820,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz",
|
||||||
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
|
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/yauzl": {
|
||||||
|
"version": "2.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz",
|
||||||
|
"integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==",
|
||||||
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.55.0",
|
"version": "5.55.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.55.0.tgz",
|
||||||
@ -11054,6 +10907,7 @@
|
|||||||
"version": "1.6.2",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
|
||||||
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
"integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
|
||||||
|
"dev": true,
|
||||||
"engines": [
|
"engines": [
|
||||||
"node >= 0.8"
|
"node >= 0.8"
|
||||||
],
|
],
|
||||||
@ -11067,12 +10921,14 @@
|
|||||||
"node_modules/concat-stream/node_modules/isarray": {
|
"node_modules/concat-stream/node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
|
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/concat-stream/node_modules/readable-stream": {
|
"node_modules/concat-stream/node_modules/readable-stream": {
|
||||||
"version": "2.3.8",
|
"version": "2.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
|
||||||
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-util-is": "~1.0.0",
|
"core-util-is": "~1.0.0",
|
||||||
"inherits": "~2.0.3",
|
"inherits": "~2.0.3",
|
||||||
@ -11087,6 +10943,7 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
@ -11178,7 +11035,7 @@
|
|||||||
"version": "1.1.12",
|
"version": "1.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
|
||||||
"integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
|
"integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ini": "^1.3.4",
|
"ini": "^1.3.4",
|
||||||
"proto-list": "~1.2.1"
|
"proto-list": "~1.2.1"
|
||||||
@ -12657,11 +12514,6 @@
|
|||||||
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
"integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/duplexer3": {
|
|
||||||
"version": "0.1.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz",
|
|
||||||
"integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA=="
|
|
||||||
},
|
|
||||||
"node_modules/duplexify": {
|
"node_modules/duplexify": {
|
||||||
"version": "3.7.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||||
@ -12736,20 +12588,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "19.1.9",
|
"version": "22.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-19.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-22.3.3.tgz",
|
||||||
"integrity": "sha512-XT5LkTzIHB+ZtD3dTmNnKjVBWrDWReCKt9G1uAFLz6uJMEVcIUiYO+fph5pLXETiBw/QZBx8egduMEfIccLx+g==",
|
"integrity": "sha512-+ZJDVfyhw7J2A46/kGKscktIhzOisTeJKrUBJLXa7PTB+U+cwyoxCBIaIOnDsdicBCX4nAc1mo6YMQjQQdAmgw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/get": "^1.14.1",
|
"@electron/get": "^2.0.0",
|
||||||
"@types/node": "^16.11.26",
|
"@types/node": "^16.11.26",
|
||||||
"extract-zip": "^1.0.3"
|
"extract-zip": "^2.0.1"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"electron": "cli.js"
|
"electron": "cli.js"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8.6"
|
"node": ">= 12.20.55"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/electron-builder": {
|
"node_modules/electron-builder": {
|
||||||
@ -13054,7 +12906,7 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
@ -15013,42 +14865,46 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/extract-zip": {
|
"node_modules/extract-zip": {
|
||||||
"version": "1.7.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
|
||||||
"integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
|
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"concat-stream": "^1.6.2",
|
"debug": "^4.1.1",
|
||||||
"debug": "^2.6.9",
|
"get-stream": "^5.1.0",
|
||||||
"mkdirp": "^0.5.4",
|
|
||||||
"yauzl": "^2.10.0"
|
"yauzl": "^2.10.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"extract-zip": "cli.js"
|
"extract-zip": "cli.js"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/extract-zip/node_modules/debug": {
|
|
||||||
"version": "2.6.9",
|
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
|
||||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
|
||||||
"dependencies": {
|
|
||||||
"ms": "2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/extract-zip/node_modules/mkdirp": {
|
|
||||||
"version": "0.5.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
|
||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
|
||||||
"dependencies": {
|
|
||||||
"minimist": "^1.2.6"
|
|
||||||
},
|
},
|
||||||
"bin": {
|
"engines": {
|
||||||
"mkdirp": "bin/cmd.js"
|
"node": ">= 10.17.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/yauzl": "^2.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/extract-zip/node_modules/ms": {
|
"node_modules/extract-zip/node_modules/get-stream": {
|
||||||
"version": "2.0.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
|
||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
|
||||||
|
"dependencies": {
|
||||||
|
"pump": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/extract-zip/node_modules/pump": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
|
"dependencies": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/extsprintf": {
|
"node_modules/extsprintf": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@ -16106,22 +15962,6 @@
|
|||||||
"node": ">=10.0"
|
"node": ">=10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/global-tunnel-ng": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"encodeurl": "^1.0.2",
|
|
||||||
"lodash": "^4.17.10",
|
|
||||||
"npm-conf": "^1.1.3",
|
|
||||||
"tunnel": "^0.0.6"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "11.12.0",
|
"version": "11.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||||
@ -24084,30 +23924,6 @@
|
|||||||
"npm-normalize-package-bin": "^1.0.1"
|
"npm-normalize-package-bin": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/npm-conf": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz",
|
|
||||||
"integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"config-chain": "^1.1.11",
|
|
||||||
"pify": "^3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/npm-conf/node_modules/pify": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/npm-install-checks": {
|
"node_modules/npm-install-checks": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-5.0.0.tgz",
|
||||||
@ -28525,14 +28341,6 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prepend-http": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.8.4",
|
"version": "2.8.4",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.4.tgz",
|
||||||
@ -28727,7 +28535,7 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
|
||||||
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
|
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
|
||||||
"devOptional": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/protocols": {
|
"node_modules/protocols": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -30131,9 +29939,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rimraf/node_modules/glob": {
|
"node_modules/rimraf/node_modules/glob": {
|
||||||
"version": "9.2.1",
|
"version": "9.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-9.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-9.3.0.tgz",
|
||||||
"integrity": "sha512-Pxxgq3W0HyA3XUvSXcFhRSs+43Jsx0ddxcFrbjxNGkL2Ak5BAUBxLqI5G6ADDeCHLfzzXFhe0b1yYcctGmytMA==",
|
"integrity": "sha512-EAZejC7JvnQINayvB/7BJbpZpNOJ8Lrw2OZNEvQxe0vaLn1SuwMcfV7/MNaX8L/T0wmptBFI4YMtDvSBxYDc7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs.realpath": "^1.0.0",
|
"fs.realpath": "^1.0.0",
|
||||||
@ -32431,14 +32239,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/to-readable-stream": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
@ -32751,16 +32551,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
"node_modules/tunnel": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/tunnel-agent": {
|
"node_modules/tunnel-agent": {
|
||||||
"version": "0.6.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||||
@ -32846,7 +32636,8 @@
|
|||||||
"node_modules/typedarray": {
|
"node_modules/typedarray": {
|
||||||
"version": "0.0.6",
|
"version": "0.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||||
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
|
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/typedoc": {
|
"node_modules/typedoc": {
|
||||||
"version": "0.23.25",
|
"version": "0.23.25",
|
||||||
@ -33170,17 +32961,6 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/url-parse-lax": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"prepend-http": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/use-isomorphic-layout-effect": {
|
"node_modules/use-isomorphic-layout-effect": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz",
|
||||||
@ -34366,6 +34146,25 @@
|
|||||||
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
"integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"packages/cluster-settings": {
|
||||||
|
"name": "@k8slens/cluster-settings",
|
||||||
|
"version": "6.5.0-alpha.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
|
"@swc/cli": "^0.1.61",
|
||||||
|
"@swc/core": "^1.3.37",
|
||||||
|
"@types/node": "^16.18.11",
|
||||||
|
"@types/semver": "^7.3.13",
|
||||||
|
"rimraf": "^4.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/cluster-settings/node_modules/@types/node": {
|
||||||
|
"version": "16.18.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.18.tgz",
|
||||||
|
"integrity": "sha512-fwGw1uvQAzabxL1pyoknPlJIF2t7+K90uTqynleKRx24n3lYcxWa3+KByLhgkF8GEAK2c7hC8Ki0RkNM5H15jQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"packages/core": {
|
"packages/core": {
|
||||||
"name": "@k8slens/core",
|
"name": "@k8slens/core",
|
||||||
"version": "6.5.0-alpha.3",
|
"version": "6.5.0-alpha.3",
|
||||||
@ -34374,6 +34173,7 @@
|
|||||||
"@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/cluster-settings": "^6.5.0-alpha.1",
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
@ -34511,7 +34311,7 @@
|
|||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.4.4",
|
"dompurify": "^2.4.4",
|
||||||
"electron": "^19.1.9",
|
"electron": "^22.3.3",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"esbuild": "^0.17.8",
|
"esbuild": "^0.17.8",
|
||||||
"esbuild-loader": "^2.21.0",
|
"esbuild-loader": "^2.21.0",
|
||||||
@ -36698,7 +36498,7 @@
|
|||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.7.2",
|
"css-loader": "^6.7.2",
|
||||||
"electron": "^19.1.9",
|
"electron": "^22.3.3",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild-loader": "^2.20.0",
|
"esbuild-loader": "^2.20.0",
|
||||||
@ -37192,7 +36992,7 @@
|
|||||||
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.9"
|
"electron": "^22.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/technical-features/application/legacy-extensions": {
|
"packages/technical-features/application/legacy-extensions": {
|
||||||
@ -37269,7 +37069,7 @@
|
|||||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.8",
|
"electron": "^22.3.3",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -37287,7 +37087,7 @@
|
|||||||
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
|
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.8",
|
"electron": "^22.3.3",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
9
packages/cluster-settings/.swcrc
Normal file
9
packages/cluster-settings/.swcrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/swcrc",
|
||||||
|
"jsc": {
|
||||||
|
"parser": {
|
||||||
|
"syntax": "typescript"
|
||||||
|
},
|
||||||
|
"target": "es2022"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
packages/cluster-settings/README.md
Normal file
3
packages/cluster-settings/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Description
|
||||||
|
|
||||||
|
The package exports tokens needed for external configuration of Cluster Settings page.
|
||||||
31
packages/cluster-settings/package.json
Normal file
31
packages/cluster-settings/package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "@k8slens/cluster-settings",
|
||||||
|
"version": "6.5.0-alpha.1",
|
||||||
|
"description": "Injection token exporter for cluster settings configuration",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"mode": "production",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist/",
|
||||||
|
"generate-types": "tsc --d --declarationDir ./dist --declarationMap --emitDeclarationOnly",
|
||||||
|
"build": "npm run generate-types && swc ./src/index.ts -d ./dist",
|
||||||
|
"prepare:test": "npm run build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
|
"@swc/cli": "^0.1.61",
|
||||||
|
"@swc/core": "^1.3.37",
|
||||||
|
"@types/node": "^16.18.11",
|
||||||
|
"@types/semver": "^7.3.13",
|
||||||
|
"rimraf": "^4.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
30
packages/cluster-settings/src/index.ts
Normal file
30
packages/cluster-settings/src/index.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
type ClusterPreferences = {
|
||||||
|
clusterName?: string;
|
||||||
|
icon?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconMenuItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
disabled?: (preferences: ClusterPreferences) => boolean;
|
||||||
|
onClick: (preferences: ClusterPreferences) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconSettingComponentProps {
|
||||||
|
preferences: ClusterPreferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterIconSettingsComponent {
|
||||||
|
id: string;
|
||||||
|
Component: React.ComponentType<ClusterIconSettingComponentProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clusterIconSettingsMenuInjectionToken = getInjectionToken<ClusterIconMenuItem>({
|
||||||
|
id: "cluster-icon-settings-menu-injection-token",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clusterIconSettingsComponentInjectionToken = getInjectionToken<ClusterIconSettingsComponent>({
|
||||||
|
id: "cluster-icon-settings-component-injection-token",
|
||||||
|
});
|
||||||
18
packages/cluster-settings/tsconfig.json
Normal file
18
packages/cluster-settings/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist/",
|
||||||
|
"paths": {
|
||||||
|
"*": [
|
||||||
|
"node_modules/*",
|
||||||
|
"types/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -120,6 +120,7 @@
|
|||||||
"@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/cluster-settings": "^6.5.0-alpha.1",
|
||||||
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
"@k8slens/node-fetch": "^6.5.0-alpha.1",
|
||||||
"@kubernetes/client-node": "^0.18.1",
|
"@kubernetes/client-node": "^0.18.1",
|
||||||
"@material-ui/styles": "^4.11.5",
|
"@material-ui/styles": "^4.11.5",
|
||||||
@ -257,7 +258,7 @@
|
|||||||
"css-loader": "^6.7.3",
|
"css-loader": "^6.7.3",
|
||||||
"deepdash": "^5.3.9",
|
"deepdash": "^5.3.9",
|
||||||
"dompurify": "^2.4.4",
|
"dompurify": "^2.4.4",
|
||||||
"electron": "^19.1.9",
|
"electron": "^22.3.3",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"esbuild": "^0.17.8",
|
"esbuild": "^0.17.8",
|
||||||
"esbuild-loader": "^2.21.0",
|
"esbuild-loader": "^2.21.0",
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export const pathNames: PathName[] = [
|
|||||||
"home",
|
"home",
|
||||||
"appData",
|
"appData",
|
||||||
"userData",
|
"userData",
|
||||||
"cache",
|
"sessionData",
|
||||||
"temp",
|
"temp",
|
||||||
"exe",
|
"exe",
|
||||||
"module",
|
"module",
|
||||||
|
|||||||
@ -21,7 +21,6 @@ describe("app-paths", () => {
|
|||||||
const defaultAppPathsStub: AppPaths = {
|
const defaultAppPathsStub: AppPaths = {
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -36,6 +35,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-irrelevant-user-data",
|
userData: "/some-irrelevant-user-data",
|
||||||
|
sessionData: "/some-irrelevant-user-data", // By default this points to userData
|
||||||
};
|
};
|
||||||
|
|
||||||
builder.beforeApplicationStart(({ mainDi }) => {
|
builder.beforeApplicationStart(({ mainDi }) => {
|
||||||
@ -73,7 +73,6 @@ describe("app-paths", () => {
|
|||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -88,6 +87,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-app-data/some-product-name",
|
userData: "/some-app-data/some-product-name",
|
||||||
|
sessionData: "/some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,7 +97,6 @@ describe("app-paths", () => {
|
|||||||
expect(actual).toEqual({
|
expect(actual).toEqual({
|
||||||
currentApp: "/some-current-app",
|
currentApp: "/some-current-app",
|
||||||
appData: "/some-app-data",
|
appData: "/some-app-data",
|
||||||
cache: "/some-cache",
|
|
||||||
crashDumps: "/some-crash-dumps",
|
crashDumps: "/some-crash-dumps",
|
||||||
desktop: "/some-desktop",
|
desktop: "/some-desktop",
|
||||||
documents: "/some-documents",
|
documents: "/some-documents",
|
||||||
@ -112,6 +111,7 @@ describe("app-paths", () => {
|
|||||||
temp: "/some-temp",
|
temp: "/some-temp",
|
||||||
videos: "/some-videos",
|
videos: "/some-videos",
|
||||||
userData: "/some-app-data/some-product-name",
|
userData: "/some-app-data/some-product-name",
|
||||||
|
sessionData: "/some-app-data/some-product-name",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { KubeConfig, V1ResourceAttributes } from "@kubernetes/client-node";
|
|
||||||
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the permissions for actions on the kube cluster
|
|
||||||
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
|
||||||
* @returns `true` if the actions described are allowed
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
export type CreateAuthorizationReview = (proxyConfig: KubeConfig) => CanI;
|
|
||||||
|
|
||||||
const createAuthorizationReviewInjectable = getInjectable({
|
|
||||||
id: "authorization-review",
|
|
||||||
instantiate: (di): CreateAuthorizationReview => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
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 createAuthorizationReviewInjectable;
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import type { KubeConfig } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type CreateAuthorizationApi = (config: KubeConfig) => AuthorizationV1Api;
|
||||||
|
|
||||||
|
const createAuthorizationApiInjectable = getInjectable({
|
||||||
|
id: "create-authorization-api",
|
||||||
|
instantiate: (): CreateAuthorizationApi => (config) => config.makeApiClient(AuthorizationV1Api),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createAuthorizationApiInjectable;
|
||||||
42
packages/core/src/common/cluster/create-can-i.injectable.ts
Normal file
42
packages/core/src/common/cluster/create-can-i.injectable.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AuthorizationV1Api, V1ResourceAttributes } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param resourceAttributes The descriptor of the action that is desired to be known if it is allowed
|
||||||
|
* @returns `true` if the actions described are allowed
|
||||||
|
*/
|
||||||
|
export type CanI = (resourceAttributes: V1ResourceAttributes) => Promise<boolean>;
|
||||||
|
|
||||||
|
export type CreateCanI = (api: AuthorizationV1Api) => CanI;
|
||||||
|
|
||||||
|
const createCanIInjectable = getInjectable({
|
||||||
|
id: "create-can-i",
|
||||||
|
instantiate: (di): CreateCanI => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return (api) => 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 createCanIInjectable;
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* 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 { CoreV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
export type CreateCoreApi = (config: KubeConfig) => CoreV1Api;
|
||||||
|
|
||||||
|
const createCoreApiInjectable = getInjectable({
|
||||||
|
id: "create-core-api",
|
||||||
|
instantiate: (): CreateCoreApi => config => config.makeApiClient(CoreV1Api),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createCoreApiInjectable;
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AuthorizationV1Api } from "@kubernetes/client-node";
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import loggerInjectable from "../logger.injectable";
|
||||||
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
|
||||||
|
export type CanListResource = (resource: KubeApiResource) => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests the permissions for actions on the kube cluster
|
||||||
|
* @param namespace The namespace of the resources
|
||||||
|
*/
|
||||||
|
export type RequestNamespaceListPermissions = (namespace: string) => Promise<CanListResource>;
|
||||||
|
|
||||||
|
export type CreateRequestNamespaceListPermissions = (api: AuthorizationV1Api) => RequestNamespaceListPermissions;
|
||||||
|
|
||||||
|
const createRequestNamespaceListPermissionsInjectable = getInjectable({
|
||||||
|
id: "create-request-namespace-list-permissions",
|
||||||
|
instantiate: (di): CreateRequestNamespaceListPermissions => {
|
||||||
|
const logger = di.inject(loggerInjectable);
|
||||||
|
|
||||||
|
return (api) => async (namespace) => {
|
||||||
|
try {
|
||||||
|
const { body: { status }} = await api.createSelfSubjectRulesReview({
|
||||||
|
apiVersion: "authorization.k8s.io/v1",
|
||||||
|
kind: "SelfSubjectRulesReview",
|
||||||
|
spec: { namespace },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!status || status.incomplete) {
|
||||||
|
logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`);
|
||||||
|
|
||||||
|
return () => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { resourceRules } = status;
|
||||||
|
|
||||||
|
return (resource) => (
|
||||||
|
resourceRules
|
||||||
|
.filter(({ apiGroups = ["*"] }) => apiGroups.includes("*") || apiGroups.includes(resource.group))
|
||||||
|
.filter(({ resources = ["*"] }) => resources.includes("*") || resources.includes(resource.apiName))
|
||||||
|
.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"))
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });
|
||||||
|
|
||||||
|
return () => true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createRequestNamespaceListPermissionsInjectable;
|
||||||
@ -2,27 +2,21 @@
|
|||||||
* 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 { KubeConfig } from "@kubernetes/client-node";
|
import type { 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 "@k8slens/utilities";
|
import { isDefined } from "@k8slens/utilities";
|
||||||
|
|
||||||
export type ListNamespaces = () => Promise<string[]>;
|
export type ListNamespaces = () => Promise<string[]>;
|
||||||
|
export type CreateListNamespaces = (api: CoreV1Api) => ListNamespaces;
|
||||||
export type CreateListNamespaces = (config: KubeConfig) => ListNamespaces;
|
|
||||||
|
|
||||||
const createListNamespacesInjectable = getInjectable({
|
const createListNamespacesInjectable = getInjectable({
|
||||||
id: "create-list-namespaces",
|
id: "create-list-namespaces",
|
||||||
instantiate: (): CreateListNamespaces => (config) => {
|
instantiate: (): CreateListNamespaces => (api) => async () => {
|
||||||
const coreApi = config.makeApiClient(CoreV1Api);
|
const { body: { items }} = await api.listNamespace();
|
||||||
|
|
||||||
return async () => {
|
return items
|
||||||
const { body: { items }} = await coreApi.listNamespace();
|
.map(ns => ns.metadata?.name)
|
||||||
|
.filter(isDefined);
|
||||||
return items
|
|
||||||
.map(ns => ns.metadata?.name)
|
|
||||||
.filter(isDefined);
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
/**
|
|
||||||
* 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 { AuthorizationV1Api } from "@kubernetes/client-node";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import loggerInjectable from "../logger.injectable";
|
|
||||||
import type { KubeApiResource } from "../rbac";
|
|
||||||
|
|
||||||
export type CanListResource = (resource: KubeApiResource) => boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests the permissions for actions on the kube cluster
|
|
||||||
* @param namespace The namespace of the resources
|
|
||||||
*/
|
|
||||||
export type RequestNamespaceListPermissions = (namespace: string) => Promise<CanListResource>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param proxyConfig This config's `currentContext` field must be set, and will be used as the target cluster
|
|
||||||
*/
|
|
||||||
export type RequestNamespaceListPermissionsFor = (proxyConfig: KubeConfig) => RequestNamespaceListPermissions;
|
|
||||||
|
|
||||||
const requestNamespaceListPermissionsForInjectable = getInjectable({
|
|
||||||
id: "request-namespace-list-permissions-for",
|
|
||||||
instantiate: (di): RequestNamespaceListPermissionsFor => {
|
|
||||||
const logger = di.inject(loggerInjectable);
|
|
||||||
|
|
||||||
return (proxyConfig) => {
|
|
||||||
const api = proxyConfig.makeApiClient(AuthorizationV1Api);
|
|
||||||
|
|
||||||
return async (namespace) => {
|
|
||||||
try {
|
|
||||||
const { body: { status }} = await api.createSelfSubjectRulesReview({
|
|
||||||
apiVersion: "authorization.k8s.io/v1",
|
|
||||||
kind: "SelfSubjectRulesReview",
|
|
||||||
spec: { namespace },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!status || status.incomplete) {
|
|
||||||
logger.warn(`[AUTHORIZATION-NAMESPACE-REVIEW]: allowing all resources in namespace="${namespace}" due to incomplete SelfSubjectRulesReview: ${status?.evaluationError}`);
|
|
||||||
|
|
||||||
return () => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { resourceRules } = status;
|
|
||||||
|
|
||||||
return (resource) => {
|
|
||||||
const rules = resourceRules.filter(({
|
|
||||||
apiGroups = ["*"],
|
|
||||||
resources = ["*"],
|
|
||||||
}) => {
|
|
||||||
const isAboutRelevantApiGroup = apiGroups.includes("*") || apiGroups.includes(resource.group);
|
|
||||||
const isAboutResource = resources.includes("*") || resources.includes(resource.apiName);
|
|
||||||
|
|
||||||
return isAboutRelevantApiGroup && isAboutResource;
|
|
||||||
});
|
|
||||||
|
|
||||||
return rules.some(({ verbs }) => verbs.includes("*") || verbs.includes("list"));
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`[AUTHORIZATION-NAMESPACE-REVIEW]: failed to create subject rules review`, { namespace, error });
|
|
||||||
|
|
||||||
return () => true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default requestNamespaceListPermissionsForInjectable;
|
|
||||||
@ -3,334 +3,225 @@
|
|||||||
* 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 { V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { AuthorizationV1Api, V1SubjectRulesReviewStatus } from "@kubernetes/client-node";
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import type { IncomingMessage } from "http";
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../main/getDiForUnitTesting";
|
||||||
import type { RequestNamespaceListPermissionsFor } from "./request-namespace-list-permissions.injectable";
|
import { cast } from "../../test-utils/cast";
|
||||||
import requestNamespaceListPermissionsForInjectable from "./request-namespace-list-permissions.injectable";
|
import type { KubeApiResource } from "../rbac";
|
||||||
|
import type { RequestNamespaceListPermissions } from "./create-request-namespace-list-permissions.injectable";
|
||||||
|
import createRequestNamespaceListPermissionsInjectable from "./create-request-namespace-list-permissions.injectable";
|
||||||
|
|
||||||
const createStubProxyConfig = (statusResponse: Promise<{ body: { status: V1SubjectRulesReviewStatus }}>) => ({
|
interface TestCase {
|
||||||
makeApiClient: () => ({
|
description: string;
|
||||||
createSelfSubjectRulesReview: (): Promise<{ body: { status: V1SubjectRulesReviewStatus }}> => statusResponse,
|
status: V1SubjectRulesReviewStatus;
|
||||||
}),
|
expected: boolean;
|
||||||
});
|
}
|
||||||
|
|
||||||
describe("requestNamespaceListPermissions", () => {
|
describe("requestNamespaceListPermissions", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let requestNamespaceListPermissions: RequestNamespaceListPermissionsFor;
|
let createSelfSubjectRulesReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectRulesReview"]>;
|
||||||
|
let requestNamespaceListPermissions: RequestNamespaceListPermissions;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
requestNamespaceListPermissions = di.inject(requestNamespaceListPermissionsForInjectable);
|
|
||||||
|
const createRequestNamespaceListPermissions = di.inject(createRequestNamespaceListPermissionsInjectable);
|
||||||
|
|
||||||
|
createSelfSubjectRulesReviewMock = asyncFn();
|
||||||
|
|
||||||
|
requestNamespaceListPermissions = createRequestNamespaceListPermissions(cast<AuthorizationV1Api>({
|
||||||
|
createSelfSubjectRulesReview: createSelfSubjectRulesReviewMock,
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when api returns incomplete data", () => {
|
describe("when a request for list permissions in a namespace has been started", () => {
|
||||||
it("returns truthy function", async () => {
|
let request: ReturnType<RequestNamespaceListPermissions>;
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
|
||||||
new Promise((resolve) => resolve({
|
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: true,
|
|
||||||
resourceRules: [],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
beforeEach(() => {
|
||||||
|
request = requestNamespaceListPermissions("irrelevant-namespace");
|
||||||
expect(permissionCheck({
|
|
||||||
apiName: "pods",
|
|
||||||
group: "",
|
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("when api rejects", () => {
|
it("should request the creation of a SelfSubjectRulesReview", () => {
|
||||||
it("returns truthy function", async () => {
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
spec: {
|
||||||
new Promise((resolve, reject) => reject("unknown error")),
|
namespace: "irrelevant-namespace",
|
||||||
) 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(
|
description: "incomplete data",
|
||||||
new Promise((resolve) => resolve({
|
status: {
|
||||||
body: {
|
incomplete: true,
|
||||||
status: {
|
resourceRules: [],
|
||||||
incomplete: false,
|
nonResourceRules: [],
|
||||||
resourceRules: [
|
},
|
||||||
{
|
expected: true,
|
||||||
apiGroups: ["*"],
|
},
|
||||||
verbs: ["*"],
|
{
|
||||||
},
|
description: "first resourceRule has all permissions for everything",
|
||||||
{
|
status: {
|
||||||
apiGroups: ["*"],
|
incomplete: false,
|
||||||
verbs: ["get"],
|
resourceRules: [
|
||||||
},
|
{
|
||||||
],
|
apiGroups: ["*"],
|
||||||
nonResourceRules: [],
|
verbs: ["*"],
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
})),
|
apiGroups: ["*"],
|
||||||
) as any);
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "first resourceRule has list permissions for everything",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "first resourceRule has list permissions for asked resource",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "last resourceRule has all permissions for everything",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "last resourceRule has list permissions for asked resource",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["*"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "resourceRules has matching resource without list verb",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: ["some-api-group"],
|
||||||
|
resources: ["some-kind"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "resourceRules has no matching resource with list verb",
|
||||||
|
status: {
|
||||||
|
incomplete: false,
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["services"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
nonResourceRules: [],
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
] as TestCase[]).forEach(({ description, status, expected }) => {
|
||||||
|
describe(`when api returns ${description}`, () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status,
|
||||||
|
spec: {},
|
||||||
|
},
|
||||||
|
response: null as unknown as IncomingMessage,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
it(`allows the request to complete, and 'canListResource' will return ${expected}`, async () => {
|
||||||
|
const canListResource = await request;
|
||||||
|
|
||||||
expect(permissionCheck({
|
expect(canListResource(someKubeResource)).toBe(expected);
|
||||||
apiName: "pods",
|
});
|
||||||
group: "",
|
});
|
||||||
kind: "Pod",
|
|
||||||
namespaced: true,
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("when first resourceRule has list permissions for everything", () => {
|
describe("when api rejects", () => {
|
||||||
it("return truthy function", async () => {
|
beforeEach(async () => {
|
||||||
const requestPermissions = requestNamespaceListPermissions(createStubProxyConfig(
|
await createSelfSubjectRulesReviewMock.reject(new Error("unknown error"));
|
||||||
new Promise((resolve) => resolve({
|
});
|
||||||
body: {
|
|
||||||
status: {
|
|
||||||
incomplete: false,
|
|
||||||
resourceRules: [
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["list"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
apiGroups: ["*"],
|
|
||||||
verbs: ["get"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
nonResourceRules: [],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
) as any);
|
|
||||||
|
|
||||||
const permissionCheck = await requestPermissions("irrelevant-namespace");
|
it("allows the request to complete, and 'canListResource' will return true", async () => {
|
||||||
|
const canListResource = await request;
|
||||||
|
|
||||||
expect(permissionCheck({
|
expect(canListResource(someKubeResource)).toBe(true);
|
||||||
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();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const someKubeResource: KubeApiResource = {
|
||||||
|
apiName: "some-kind",
|
||||||
|
group: "some-api-group",
|
||||||
|
kind: "SomeKind",
|
||||||
|
namespaced: true,
|
||||||
|
};
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import userStoreInjectable from "../user-store/user-store.injectable";
|
|||||||
|
|
||||||
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void;
|
||||||
|
|
||||||
const mapProcessName = (type: "browser" | "renderer" | "worker") => type === "browser" ? "main" : type;
|
const mapProcessName = (type: "browser" | "renderer" | "worker" | "utility") => type === "browser" ? "main" : type;
|
||||||
|
|
||||||
const initializeSentryReportingWithInjectable = getInjectable({
|
const initializeSentryReportingWithInjectable = getInjectable({
|
||||||
id: "initialize-sentry-reporting-with",
|
id: "initialize-sentry-reporting-with",
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import copyInjectable from "./copy.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(copyInjectable, () => async () => {
|
|
||||||
throw new Error("tried to copy filepaths without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import lstatInjectable from "./lstat.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(lstatInjectable, () => async () => {
|
|
||||||
throw new Error("tried to lstat a filepath without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import readDirectoryInjectable from "./read-directory.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(readDirectoryInjectable, () => async () => {
|
|
||||||
throw new Error("tried to read a directory's content without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import removePathInjectable from "./remove.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(removePathInjectable, () => async () => {
|
|
||||||
throw new Error("tried to remove path without override");
|
|
||||||
});
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getGlobalOverride } from "@k8slens/test-utils";
|
|
||||||
import writeFileInjectable from "./write-file.injectable";
|
|
||||||
|
|
||||||
export default getGlobalOverride(writeFileInjectable, () => async () => {
|
|
||||||
throw new Error("tried to write file without override");
|
|
||||||
});
|
|
||||||
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
import type { DiContainer } from "@ogre-tools/injectable";
|
||||||
|
import { getInjectable } from "@ogre-tools/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";
|
||||||
@ -21,6 +22,9 @@ import maybeKubeApiInjectable from "../maybe-kube-api.injectable";
|
|||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
|
import { KubeApi as ExternalKubeApi } from "../../../extensions/common-api/k8s-api";
|
||||||
import { Cluster } from "../../cluster/cluster";
|
import { Cluster } from "../../cluster/cluster";
|
||||||
|
import { runInAction } from "mobx";
|
||||||
|
import { customResourceDefinitionApiInjectionToken } from "../api-manager/crd-api-token";
|
||||||
|
import assert from "assert";
|
||||||
|
|
||||||
class TestApi extends KubeApi<KubeObject> {
|
class TestApi extends KubeApi<KubeObject> {
|
||||||
protected async checkPreferredVersion() {
|
protected async checkPreferredVersion() {
|
||||||
@ -117,4 +121,90 @@ describe("ApiManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given than a CRD has a default KubeApi registered for it", () => {
|
||||||
|
const apiBase = "/apis/aquasecurity.github.io/v1alpha1/vulnerabilityreports";
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(getInjectable({
|
||||||
|
id: `default-kube-api-for-custom-resource-definition-${apiBase}`,
|
||||||
|
instantiate: (di) => {
|
||||||
|
const objectConstructor = class extends KubeObject {
|
||||||
|
static readonly kind = "VulnerabilityReport";
|
||||||
|
static readonly namespaced = true;
|
||||||
|
static readonly apiBase = apiBase;
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.assign(
|
||||||
|
new KubeApi({
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
maybeKubeApi: di.inject(maybeKubeApiInjectable),
|
||||||
|
}, { objectConstructor }),
|
||||||
|
{
|
||||||
|
myField: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
injectionToken: customResourceDefinitionApiInjectionToken,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can be retrieved from apiManager", () => {
|
||||||
|
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||||
|
myField: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given that an extension registers an api with the same apibase", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
void Object.assign(new ExternalKubeApi({
|
||||||
|
objectConstructor: KubeObject,
|
||||||
|
apiBase,
|
||||||
|
kind: "VulnerabilityReport",
|
||||||
|
}), {
|
||||||
|
myField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("the extension's instance is retrievable instead from apiManager", () => {
|
||||||
|
expect(apiManager.getApi(apiBase)).toMatchObject({
|
||||||
|
myField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can have a default KubeObjectStore instance retrieved for it", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toBeInstanceOf(KubeObjectStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given that an extension registers a store for the same apibase", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const api = apiManager.getApi(apiBase);
|
||||||
|
|
||||||
|
assert(api);
|
||||||
|
|
||||||
|
apiManager.registerStore(Object.assign(
|
||||||
|
new KubeObjectStore({
|
||||||
|
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
}, api),
|
||||||
|
{
|
||||||
|
someField: 2,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can gets the custom KubeObjectStore instance instead", () => {
|
||||||
|
expect(apiManager.getStore(apiBase)).toMatchObject({
|
||||||
|
someField: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,7 +10,8 @@ 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 { iter } from "@k8slens/utilities";
|
import { getOrInsertWith, iter } from "@k8slens/utilities";
|
||||||
|
import type { CreateCustomResourceStore } from "./create-custom-resource-store.injectable";
|
||||||
|
|
||||||
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
|
export type RegisterableStore<Store> = Store extends KubeObjectStore<any, any, any>
|
||||||
? Store
|
? Store
|
||||||
@ -26,13 +27,15 @@ export type FindApiCallback = (api: KubeApi<KubeObject>) => boolean;
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
readonly apis: IComputedValue<KubeApi[]>;
|
readonly apis: IComputedValue<KubeApi[]>;
|
||||||
|
readonly crdApis: IComputedValue<KubeApi[]>;
|
||||||
readonly stores: IComputedValue<KubeObjectStore[]>;
|
readonly stores: IComputedValue<KubeObjectStore[]>;
|
||||||
|
createCustomResourceStore: CreateCustomResourceStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiManager {
|
export class ApiManager {
|
||||||
private readonly externalApis = observable.array<KubeApi>();
|
private readonly externalApis = observable.array<KubeApi>();
|
||||||
private readonly externalStores = observable.array<KubeObjectStore>();
|
private readonly externalStores = observable.array<KubeObjectStore>();
|
||||||
|
private readonly defaultCrdStores = observable.map<string, KubeObjectStore>();
|
||||||
private readonly apis = observable.map<string, KubeApi>();
|
private readonly apis = observable.map<string, KubeApi>();
|
||||||
|
|
||||||
constructor(private readonly dependencies: Dependencies) {
|
constructor(private readonly dependencies: Dependencies) {
|
||||||
@ -56,6 +59,12 @@ export class ApiManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||||
|
if (!newState.has(crdApi.apiBase)) {
|
||||||
|
newState.set(crdApi.apiBase, crdApi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.apis.replace(newState);
|
this.apis.replace(newState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -110,6 +119,16 @@ export class ApiManager {
|
|||||||
this.externalStores.push(store);
|
this.externalStores.push(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private apiIsDefaultCrdApi(api: KubeApi): boolean {
|
||||||
|
for (const crdApi of this.dependencies.crdApis.get()) {
|
||||||
|
if (crdApi.apiBase === api.apiBase) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
getStore(api: string | undefined): KubeObjectStore | undefined;
|
getStore(api: string | undefined): KubeObjectStore | undefined;
|
||||||
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
|
getStore<Api>(api: RegisterableApi<Api>): KubeObjectStoreFrom<Api> | undefined;
|
||||||
/**
|
/**
|
||||||
@ -130,9 +149,19 @@ export class ApiManager {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return iter.chain(this.dependencies.stores.get().values())
|
const defaultResult = 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);
|
||||||
|
|
||||||
|
if (defaultResult) {
|
||||||
|
return defaultResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiIsDefaultCrdApi(api)) {
|
||||||
|
return getOrInsertWith(this.defaultCrdStores, api.apiBase, () => this.dependencies.createCustomResourceStore(api));
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
|
lookupApiLink(ref: ObjectReference, parentObject?: KubeObject): string {
|
||||||
|
|||||||
@ -5,11 +5,9 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import type TypedEventEmitter from "typed-emitter";
|
import type TypedEventEmitter from "typed-emitter";
|
||||||
import type { CustomResourceDefinition } from "../endpoints";
|
|
||||||
import type { KubeApi } from "../kube-api";
|
import type { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
export interface LegacyAutoRegistration {
|
export interface LegacyAutoRegistration {
|
||||||
customResourceDefinition: (crd: CustomResourceDefinition) => void;
|
|
||||||
kubeApi: (api: KubeApi<any, any>) => void;
|
kubeApi: (api: KubeApi<any, any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* 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 { KubeApi } from "../kube-api";
|
||||||
|
|
||||||
|
export const customResourceDefinitionApiInjectionToken = getInjectionToken<KubeApi>({
|
||||||
|
id: "custom-resource-definition-api-token",
|
||||||
|
});
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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 clusterFrameContextForNamespacedResourcesInjectable from "../../../renderer/cluster-frame-context/for-namespaced-resources.injectable";
|
||||||
|
import loggerInjectable from "../../logger.injectable";
|
||||||
|
import type { KubeApi } from "../kube-api";
|
||||||
|
import type { KubeObject } from "../kube-object";
|
||||||
|
import type { KubeObjectStoreDependencies } from "../kube-object.store";
|
||||||
|
import { CustomResourceStore } from "./resource.store";
|
||||||
|
|
||||||
|
export type CreateCustomResourceStore = <K extends KubeObject>(api: KubeApi<K>) => CustomResourceStore<K>;
|
||||||
|
|
||||||
|
const createCustomResourceStoreInjectable = getInjectable({
|
||||||
|
id: "create-custom-resource-store",
|
||||||
|
instantiate: (di): CreateCustomResourceStore => {
|
||||||
|
const deps: KubeObjectStoreDependencies = {
|
||||||
|
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (api) => new CustomResourceStore(deps, api);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default createCustomResourceStoreInjectable;
|
||||||
@ -9,6 +9,8 @@ import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-f
|
|||||||
import { kubeObjectStoreInjectionToken } from "./kube-object-store-token";
|
import { kubeObjectStoreInjectionToken } from "./kube-object-store-token";
|
||||||
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
|
import { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token";
|
||||||
import { computed } from "mobx";
|
import { computed } from "mobx";
|
||||||
|
import { customResourceDefinitionApiInjectionToken } from "./crd-api-token";
|
||||||
|
import createCustomResourceStoreInjectable from "./create-custom-resource-store.injectable";
|
||||||
|
|
||||||
const apiManagerInjectable = getInjectable({
|
const apiManagerInjectable = getInjectable({
|
||||||
id: "api-manager",
|
id: "api-manager",
|
||||||
@ -23,6 +25,10 @@ const apiManagerInjectable = getInjectable({
|
|||||||
stores: storesAndApisCanBeCreated
|
stores: storesAndApisCanBeCreated
|
||||||
? computedInjectMany(kubeObjectStoreInjectionToken)
|
? computedInjectMany(kubeObjectStoreInjectionToken)
|
||||||
: computed(() => []),
|
: computed(() => []),
|
||||||
|
crdApis: storesAndApisCanBeCreated
|
||||||
|
? computedInjectMany(customResourceDefinitionApiInjectionToken)
|
||||||
|
: computed(() => []),
|
||||||
|
createCustomResourceStore: di.inject(createCustomResourceStoreInjectable),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -88,7 +88,7 @@ export interface KubeObjectStoreDependencies {
|
|||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class KubeObjectStore<
|
export class KubeObjectStore<
|
||||||
K extends KubeObject = KubeObject,
|
K extends KubeObject = KubeObject,
|
||||||
A extends KubeApi<K, D> = KubeApi<K, KubeJsonApiDataFor<K>>,
|
A extends KubeApi<K, D> = KubeApi<K, KubeJsonApiDataFor<K>>,
|
||||||
D extends KubeJsonApiDataFor<K> = KubeApiDataFrom<K, A>,
|
D extends KubeJsonApiDataFor<K> = KubeApiDataFrom<K, A>,
|
||||||
|
|||||||
25
packages/core/src/common/utils/registrator-helper.ts
Normal file
25
packages/core/src/common/utils/registrator-helper.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { iter } from "@k8slens/utilities";
|
||||||
|
import type { DiContainerForInjection, Injectable } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
|
// Register new injectables and deregister removed injectables by id
|
||||||
|
|
||||||
|
export const injectableDifferencingRegistratorWith = (di: DiContainerForInjection) => (
|
||||||
|
(rawCurrent: Injectable<any, any, any>[], rawPrevious: Injectable<any, any, any>[] = []) => {
|
||||||
|
const current = new Map(rawCurrent.map(inj => [inj.id, inj]));
|
||||||
|
const previous = new Map(rawPrevious.map(inj => [inj.id, inj]));
|
||||||
|
const toAdd = iter.chain(current.entries())
|
||||||
|
.filter(([id]) => !previous.has(id))
|
||||||
|
.collect(entries => new Map(entries));
|
||||||
|
const toRemove = iter.chain(previous.entries())
|
||||||
|
.filter(([id]) => !current.has(id))
|
||||||
|
.collect(entries => new Map(entries));
|
||||||
|
|
||||||
|
di.deregister(...toRemove.values());
|
||||||
|
di.register(...toAdd.values());
|
||||||
|
}
|
||||||
|
);
|
||||||
@ -2,29 +2,18 @@
|
|||||||
* 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 { Injectable } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import { difference, find, map } from "lodash";
|
|
||||||
import { reaction, runInAction } from "mobx";
|
import { reaction, runInAction } from "mobx";
|
||||||
import { disposer } from "@k8slens/utilities";
|
import { disposer } from "@k8slens/utilities";
|
||||||
import type { LensExtension } from "../../lens-extension";
|
import type { LensExtension } from "../../lens-extension";
|
||||||
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
import { extensionRegistratorInjectionToken } from "../extension-registrator-injection-token";
|
||||||
|
import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper";
|
||||||
|
|
||||||
export interface Extension {
|
export interface Extension {
|
||||||
register: () => void;
|
register: () => void;
|
||||||
deregister: () => void;
|
deregister: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const idsToInjectables = (ids: string[], injectables: Injectable<any, any, any>[]) => ids.map(id => {
|
|
||||||
const injectable = find(injectables, { id });
|
|
||||||
|
|
||||||
if (!injectable) {
|
|
||||||
throw new Error(`Injectable ${id} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return injectable;
|
|
||||||
});
|
|
||||||
|
|
||||||
const extensionInjectable = getInjectable({
|
const extensionInjectable = getInjectable({
|
||||||
id: "extension",
|
id: "extension",
|
||||||
|
|
||||||
@ -35,36 +24,27 @@ const extensionInjectable = getInjectable({
|
|||||||
instantiate: (childDi) => {
|
instantiate: (childDi) => {
|
||||||
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
|
const extensionRegistrators = childDi.injectMany(extensionRegistratorInjectionToken);
|
||||||
const reactionDisposer = disposer();
|
const reactionDisposer = disposer();
|
||||||
|
const injectableDifferencingRegistrator = injectableDifferencingRegistratorWith(childDi);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
register: () => {
|
register: () => {
|
||||||
extensionRegistrators.forEach((getInjectablesOfExtension) => {
|
for (const extensionRegistrator of extensionRegistrators) {
|
||||||
const injectables = getInjectablesOfExtension(instance);
|
const injectables = extensionRegistrator(instance);
|
||||||
|
|
||||||
reactionDisposer.push(
|
if (Array.isArray(injectables)) {
|
||||||
// injectables is either an array or a computed array, in which case
|
runInAction(() => {
|
||||||
// we need to update the registered injectables with a reaction every time they change
|
injectableDifferencingRegistrator(injectables);
|
||||||
reaction(
|
});
|
||||||
() => Array.isArray(injectables) ? injectables : injectables.get(),
|
} else {
|
||||||
(currentInjectables, previousInjectables = []) => {
|
reactionDisposer.push(reaction(
|
||||||
// Register new injectables and deregister removed injectables by id
|
() => injectables.get(),
|
||||||
const currentIds = map(currentInjectables, "id");
|
injectableDifferencingRegistrator,
|
||||||
const previousIds = map(previousInjectables, "id");
|
{
|
||||||
const idsToAdd = difference(currentIds, previousIds);
|
|
||||||
const idsToRemove = previousIds.filter(previousId => !currentIds.includes(previousId));
|
|
||||||
|
|
||||||
if (idsToRemove.length > 0) {
|
|
||||||
childDi.deregister(...idsToInjectables(idsToRemove, previousInjectables));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idsToAdd.length > 0) {
|
|
||||||
childDi.register(...idsToInjectables(idsToAdd, currentInjectables));
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
fireImmediately: true,
|
fireImmediately: true,
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
});
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deregister: () => {
|
deregister: () => {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const checkForUpdatesMenuItemInjectable = getInjectable({
|
|||||||
id: "check-for-updates",
|
id: "check-for-updates",
|
||||||
parentId: isMac ? "mac" : "help",
|
parentId: isMac ? "mac" : "help",
|
||||||
orderNumber: isMac ? 20 : 50,
|
orderNumber: isMac ? 20 : 50,
|
||||||
label: "Check for updates",
|
label: "Check for Updates...",
|
||||||
isShown: updatingIsEnabled,
|
isShown: updatingIsEnabled,
|
||||||
|
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
|
|||||||
@ -146,7 +146,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates indicates that checking is happening", () => {
|
it("name of tray item for checking updates indicates that checking is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Checking for updates...");
|
).toBe("Checking for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("user cannot install update yet", () => {
|
it("user cannot install update yet", () => {
|
||||||
@ -177,7 +177,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that checking is happening", () => {
|
it("name of tray item for checking updates no longer indicates that checking is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -241,7 +241,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
@ -269,7 +269,7 @@ describe("installing update using tray", () => {
|
|||||||
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
it("name of tray item for checking updates no longer indicates that downloading is happening", () => {
|
||||||
expect(
|
expect(
|
||||||
builder.tray.get("check-for-updates")?.label,
|
builder.tray.get("check-for-updates")?.label,
|
||||||
).toBe("Check for updates");
|
).toBe("Check for Updates...");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders", () => {
|
it("renders", () => {
|
||||||
|
|||||||
@ -49,10 +49,10 @@ const checkForUpdatesTrayItemInjectable = getInjectable({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (checkingForUpdatesState.value.get()) {
|
if (checkingForUpdatesState.value.get()) {
|
||||||
return "Checking for updates...";
|
return "Checking for Updates...";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Check for updates";
|
return "Check for Updates...";
|
||||||
}),
|
}),
|
||||||
|
|
||||||
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
|
enabled: computed(() => !checkingForUpdatesState.value.get() && !downloadingUpdateState.value.get()),
|
||||||
|
|||||||
@ -0,0 +1,637 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
import asyncFn from "@async-fn/jest";
|
||||||
|
import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node";
|
||||||
|
import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable";
|
||||||
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
|
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
|
||||||
|
import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable";
|
||||||
|
import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder";
|
||||||
|
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||||
|
import type { PartialDeep } from "type-fest";
|
||||||
|
import { anyObject } from "jest-mock-extended";
|
||||||
|
import createCoreApiInjectable from "../../common/cluster/create-core-api.injectable";
|
||||||
|
import type { K8sRequest } from "../../main/k8s-request.injectable";
|
||||||
|
import k8sRequestInjectable from "../../main/k8s-request.injectable";
|
||||||
|
import type { DetectClusterMetadata } from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
|
import detectClusterMetadataInjectable from "../../main/cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
|
import type { ClusterConnection } from "../../main/cluster/cluster-connection.injectable";
|
||||||
|
import clusterConnectionInjectable from "../../main/cluster/cluster-connection.injectable";
|
||||||
|
import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
import type { Mocked } from "../../test-utils/mock-interface";
|
||||||
|
import { flushPromises } from "@k8slens/test-utils";
|
||||||
|
|
||||||
|
describe("Refresh Cluster Accessibility Technical Tests", () => {
|
||||||
|
let builder: ApplicationBuilder;
|
||||||
|
let createSelfSubjectRulesReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectRulesReview"]>;
|
||||||
|
let createSelfSubjectAccessReviewMock: AsyncFnMock<AuthorizationV1Api["createSelfSubjectAccessReview"]>;
|
||||||
|
let listNamespaceMock: AsyncFnMock<CoreV1Api["listNamespace"]>;
|
||||||
|
let k8sRequestMock: AsyncFnMock<K8sRequest>;
|
||||||
|
let detectClusterMetadataMock: AsyncFnMock<DetectClusterMetadata>;
|
||||||
|
let kubeAuthProxyMock: Mocked<KubeAuthProxy>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
builder = getApplicationBuilder();
|
||||||
|
|
||||||
|
const mainDi = builder.mainDi;
|
||||||
|
|
||||||
|
mainDi.override(broadcastMessageInjectable, () => async () => {});
|
||||||
|
|
||||||
|
kubeAuthProxyMock = {
|
||||||
|
apiPrefix: "/some-api-prefix",
|
||||||
|
port: 0,
|
||||||
|
exit: jest.fn(),
|
||||||
|
run: asyncFn(),
|
||||||
|
};
|
||||||
|
mainDi.override(createKubeAuthProxyInjectable, () => () => kubeAuthProxyMock);
|
||||||
|
|
||||||
|
detectClusterMetadataMock = asyncFn();
|
||||||
|
mainDi.override(detectClusterMetadataInjectable, () => detectClusterMetadataMock);
|
||||||
|
|
||||||
|
k8sRequestMock = asyncFn();
|
||||||
|
mainDi.override(k8sRequestInjectable, () => k8sRequestMock);
|
||||||
|
|
||||||
|
createSelfSubjectRulesReviewMock = asyncFn();
|
||||||
|
createSelfSubjectAccessReviewMock = asyncFn();
|
||||||
|
mainDi.override(createAuthorizationApiInjectable, () => () => ({
|
||||||
|
createSelfSubjectRulesReview: createSelfSubjectRulesReviewMock,
|
||||||
|
createSelfSubjectAccessReview: createSelfSubjectAccessReviewMock,
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
listNamespaceMock = asyncFn();
|
||||||
|
mainDi.override(createCoreApiInjectable, () => () => ({
|
||||||
|
listNamespace: listNamespaceMock,
|
||||||
|
} as any));
|
||||||
|
|
||||||
|
await builder.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given a cluster with no configured preferences", () => {
|
||||||
|
let cluster: Cluster;
|
||||||
|
let clusterConnection: ClusterConnection;
|
||||||
|
let refreshPromise: Promise<void>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const mainDi = builder.mainDi;
|
||||||
|
const clusterStore = mainDi.inject(clusterStoreInjectable);
|
||||||
|
const writeJsonFile = mainDi.inject(writeJsonFileInjectable);
|
||||||
|
|
||||||
|
await writeJsonFile("/some-kube-config-path", {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Config",
|
||||||
|
clusters: [{
|
||||||
|
name: "some-cluster-name",
|
||||||
|
cluster: {
|
||||||
|
server: "https://localhost:8989",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
users: [{
|
||||||
|
name: "some-user-name",
|
||||||
|
}],
|
||||||
|
contexts: [{
|
||||||
|
name: "some-cluster-context",
|
||||||
|
context: {
|
||||||
|
user: "some-user-name",
|
||||||
|
cluster: "some-cluster-name",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterStore.addCluster({
|
||||||
|
contextName: "some-cluster-context",
|
||||||
|
id: "some-cluster-id",
|
||||||
|
kubeConfigPath: "/some-kube-config-path",
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster = clusterStore.getById("some-cluster-id") ?? (() => { throw new Error("missing cluster"); })();
|
||||||
|
clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster);
|
||||||
|
refreshPromise = clusterConnection.refreshAccessibilityAndMetadata();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("starts kubeAuthProxy", () => {
|
||||||
|
expect(kubeAuthProxyMock.run).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when kubeAuthProxy has started running and its port is found", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
kubeAuthProxyMock.port = 1235;
|
||||||
|
await kubeAuthProxyMock.run.resolve();
|
||||||
|
await flushPromises();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests if cluster has admin permissions", async () => {
|
||||||
|
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "kube-system",
|
||||||
|
resource: "*",
|
||||||
|
verb: "create",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([ true, false ])("when cluster admin request resolves to %p", (isAdmin) => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectAccessReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
allowed: isAdmin,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectAccessReview>,
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests if cluster has global watch permissions", () => {
|
||||||
|
expect(createSelfSubjectAccessReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
verb: "watch",
|
||||||
|
resource: "*",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.each([ true, false ])("when cluster global watch request resolves with %p", (globalWatch) => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectAccessReviewMock.resolve({
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
allowed: globalWatch,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectAccessReview>,
|
||||||
|
} as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespaces", () => {
|
||||||
|
expect(listNamespaceMock).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when list namespaces resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await listNamespaceMock.resolve(listNamespaceResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests core api versions", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/api",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when core api versions request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve({
|
||||||
|
serverAddressByClientCIDRs: [],
|
||||||
|
versions: [
|
||||||
|
"v1",
|
||||||
|
],
|
||||||
|
} as V1APIVersions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests non-core api resource kinds", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when non-core api resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(nonCoreApiResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resource kinds in core", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/api/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when core specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(coreApiKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resources kinds from the first non-core response", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis/node.k8s.io/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when first specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(nodeK8sIoKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests specific resources kinds from the second non-core response", () => {
|
||||||
|
expect(k8sRequestMock).toBeCalledWith(
|
||||||
|
anyObject({ id: "some-cluster-id" }),
|
||||||
|
"/apis/discovery.k8s.io/v1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when second specific resource kinds request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await k8sRequestMock.resolve(discoveryK8sIoKindsResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'default' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "default",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions are incomplete", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultIncompletePermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to an empty list", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to a single entry with 'list' verb", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultSingleListPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(defaultMultipleListPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests namespace list permissions for 'my-namespace' namespace", () => {
|
||||||
|
expect(createSelfSubjectRulesReviewMock).toBeCalledWith(anyObject({
|
||||||
|
spec: {
|
||||||
|
namespace: "my-namespace",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when the permissions request for 'my-namespace' resolves as empty", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createSelfSubjectRulesReviewMock.resolve(emptyPermissions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requests cluster metadata", () => {
|
||||||
|
expect(detectClusterMetadataMock).toBeCalledWith(anyObject({ id: "some-cluster-id" }));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when cluster metadata request resolves", () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await detectClusterMetadataMock.resolve({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the call to refreshAccessibilityAndMetadata to resolve", async () => {
|
||||||
|
await refreshPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster displaying 'pods'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("pods")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have the cluster not displaying 'namespaces'", () => {
|
||||||
|
expect(cluster.resourcesToShow.has("namespaces")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when the permissions are incomplete", () => {});
|
||||||
|
describe.skip("when the permissions resolve to a single entry with 'list' verb", () => {});
|
||||||
|
describe.skip("when the permissions resolve to multiple entries with the 'list' verb not on the first entry", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when second specific resource kinds rejects", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.skip("when first specific resource kinds rejects", () => {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const nonCoreApiResponse = {
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: "node.k8s.io",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preferredVersion: {
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "discovery.k8s.io",
|
||||||
|
versions: [
|
||||||
|
{
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
preferredVersion: {
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
version: "v1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as V1APIGroupList;
|
||||||
|
|
||||||
|
const listNamespaceResponse = {
|
||||||
|
body: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: "default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: {
|
||||||
|
name: "my-namespace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as PartialDeep<V1NamespaceList>,
|
||||||
|
} as Awaited<ReturnType<CoreV1Api["listNamespace"]>>;
|
||||||
|
|
||||||
|
const coreApiKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
groupVersion: "v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "namespaces",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: false,
|
||||||
|
kind: "Namespace",
|
||||||
|
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
|
||||||
|
shortNames: ["ns"],
|
||||||
|
storageVersionHash: "Q3oi5N2YM8M=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "Pod",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
shortNames: ["po"],
|
||||||
|
categories: ["all"],
|
||||||
|
storageVersionHash: "xPOwRZ+Yhw8=",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pods/attach",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "PodAttachOptions",
|
||||||
|
verbs: ["create", "get"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeK8sIoKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
apiVersion: "v1",
|
||||||
|
groupVersion: "node.k8s.io/v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "runtimeclasses",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: false,
|
||||||
|
kind: "RuntimeClass",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
storageVersionHash: "WQTu1GL3T2Q=",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const discoveryK8sIoKindsResponse = {
|
||||||
|
kind: "APIResourceList",
|
||||||
|
apiVersion: "v1",
|
||||||
|
groupVersion: "discovery.k8s.io/v1",
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
name: "endpointslices",
|
||||||
|
singularName: "",
|
||||||
|
namespaced: true,
|
||||||
|
kind: "EndpointSlice",
|
||||||
|
verbs: [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch",
|
||||||
|
],
|
||||||
|
storageVersionHash: "Nx3SIv6I0mE=",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
type CreateSelfSubjectRulesReviewRes = Awaited<ReturnType<AuthorizationV1Api["createSelfSubjectRulesReview"]>>;
|
||||||
|
|
||||||
|
const defaultIncompletePermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
incomplete: true,
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const emptyPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const defaultSingleListPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["list"],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
|
|
||||||
|
const defaultMultipleListPermissions = {
|
||||||
|
body: {
|
||||||
|
status: {
|
||||||
|
resourceRules: [
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["get"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiGroups: [""],
|
||||||
|
resources: ["pods"],
|
||||||
|
verbs: ["list"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as PartialDeep<V1SelfSubjectRulesReview>,
|
||||||
|
} as CreateSelfSubjectRulesReviewRes;
|
||||||
@ -31,8 +31,7 @@ describe("extension special characters in page registrations", () => {
|
|||||||
|
|
||||||
describe("when navigating to route with ID having special characters", () => {
|
describe("when navigating to route with ID having special characters", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const testExtension =
|
const testExtension = builder.extensions.get("some-extension-id").applicationWindows.only;
|
||||||
builder.extensions.get("some-extension-id").applicationWindows.only;
|
|
||||||
|
|
||||||
testExtension.navigate("/some-page-id/");
|
testExtension.navigate("/some-page-id/");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -5,10 +5,6 @@
|
|||||||
import { Cluster } from "../../common/cluster/cluster";
|
import { Cluster } from "../../common/cluster/cluster";
|
||||||
import { Kubectl } from "../kubectl/kubectl";
|
import { Kubectl } from "../kubectl/kubectl";
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
import createAuthorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
|
||||||
import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable";
|
|
||||||
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
|
||||||
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable";
|
|
||||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
||||||
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable";
|
||||||
@ -19,6 +15,10 @@ import clusterConnectionInjectable from "../cluster/cluster-connection.injectabl
|
|||||||
import kubeconfigManagerInjectable from "../kubeconfig-manager/kubeconfig-manager.injectable";
|
import kubeconfigManagerInjectable from "../kubeconfig-manager/kubeconfig-manager.injectable";
|
||||||
import type { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
|
import type { KubeconfigManager } from "../kubeconfig-manager/kubeconfig-manager";
|
||||||
import broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable";
|
import broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable";
|
||||||
|
import createCanIInjectable from "../../common/cluster/create-can-i.injectable";
|
||||||
|
import createRequestNamespaceListPermissionsInjectable from "../../common/cluster/create-request-namespace-list-permissions.injectable";
|
||||||
|
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
|
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable";
|
||||||
|
|
||||||
describe("create clusters", () => {
|
describe("create clusters", () => {
|
||||||
let cluster: Cluster;
|
let cluster: Cluster;
|
||||||
@ -34,8 +34,8 @@ describe("create clusters", () => {
|
|||||||
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
di.override(kubectlDownloadingNormalizedArchInjectable, () => "amd64");
|
||||||
di.override(normalizedPlatformInjectable, () => "darwin");
|
di.override(normalizedPlatformInjectable, () => "darwin");
|
||||||
di.override(broadcastConnectionUpdateInjectable, () => async () => {});
|
di.override(broadcastConnectionUpdateInjectable, () => async () => {});
|
||||||
di.override(createAuthorizationReviewInjectable, () => () => () => Promise.resolve(true));
|
di.override(createCanIInjectable, () => () => () => Promise.resolve(true));
|
||||||
di.override(requestNamespaceListPermissionsForInjectable, () => () => async () => () => true);
|
di.override(createRequestNamespaceListPermissionsInjectable, () => () => async () => () => true);
|
||||||
di.override(createListNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
di.override(createListNamespacesInjectable, () => () => () => Promise.resolve([ "default" ]));
|
||||||
di.override(prometheusHandlerInjectable, () => ({
|
di.override(prometheusHandlerInjectable, () => ({
|
||||||
getPrometheusDetails: jest.fn(),
|
getPrometheusDetails: jest.fn(),
|
||||||
|
|||||||
@ -1,203 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
|
||||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
|
||||||
import { Cluster } from "../../common/cluster/cluster";
|
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
|
||||||
import type { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { PrometheusProvider } from "../prometheus/provider";
|
|
||||||
import { prometheusProviderInjectionToken } from "../prometheus/provider";
|
|
||||||
import { runInAction } from "mobx";
|
|
||||||
import prometheusHandlerInjectable from "../cluster/prometheus-handler/prometheus-handler.injectable";
|
|
||||||
import directoryForTempInjectable from "../../common/app-paths/directory-for-temp/directory-for-temp.injectable";
|
|
||||||
import lensProxyPortInjectable from "../lens-proxy/lens-proxy-port.injectable";
|
|
||||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
|
||||||
import loadProxyKubeconfigInjectable from "../cluster/load-proxy-kubeconfig.injectable";
|
|
||||||
import type { KubeConfig } from "@kubernetes/client-node";
|
|
||||||
|
|
||||||
enum ServiceResult {
|
|
||||||
Success,
|
|
||||||
Failure,
|
|
||||||
}
|
|
||||||
|
|
||||||
const createTestPrometheusProvider = (kind: string, alwaysFail: ServiceResult): PrometheusProvider => ({
|
|
||||||
kind,
|
|
||||||
name: "TestProvider1",
|
|
||||||
isConfigurable: false,
|
|
||||||
getQuery: () => {
|
|
||||||
throw new Error("getQuery is not implemented.");
|
|
||||||
},
|
|
||||||
getPrometheusService: async () => {
|
|
||||||
switch (alwaysFail) {
|
|
||||||
case ServiceResult.Success:
|
|
||||||
return {
|
|
||||||
kind,
|
|
||||||
namespace: "default",
|
|
||||||
port: 7000,
|
|
||||||
service: "",
|
|
||||||
};
|
|
||||||
case ServiceResult.Failure:
|
|
||||||
throw new Error("does fail");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ContextHandler", () => {
|
|
||||||
let di: DiContainer;
|
|
||||||
let cluster: Cluster;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
di = getDiForUnitTesting();
|
|
||||||
|
|
||||||
di.override(loadProxyKubeconfigInjectable, () => async () => ({
|
|
||||||
makeApiClient: () => ({} as any),
|
|
||||||
} as Partial<KubeConfig>));
|
|
||||||
|
|
||||||
di.override(createKubeAuthProxyInjectable, () => () => ({
|
|
||||||
run: async () => {},
|
|
||||||
} as KubeAuthProxy));
|
|
||||||
di.override(directoryForTempInjectable, () => "/some-directory-for-tmp");
|
|
||||||
di.inject(lensProxyPortInjectable).set(9968);
|
|
||||||
|
|
||||||
cluster = new Cluster({
|
|
||||||
contextName: "some-context-name",
|
|
||||||
id: "some-cluster-id",
|
|
||||||
kubeConfigPath: "/some-kubeconfig-path",
|
|
||||||
}, {
|
|
||||||
clusterServerUrl: "https://some-website.com",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("getPrometheusService", () => {
|
|
||||||
it.each([
|
|
||||||
[0],
|
|
||||||
[1],
|
|
||||||
[2],
|
|
||||||
[3],
|
|
||||||
])("should throw after %d failure(s)", async (failures) => {
|
|
||||||
runInAction(() => {
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-failure-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(() => di.inject(prometheusHandlerInjectable, cluster).getPrometheusDetails()).rejects.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[1, 0],
|
|
||||||
[1, 1],
|
|
||||||
[1, 2],
|
|
||||||
[1, 3],
|
|
||||||
[2, 0],
|
|
||||||
[2, 1],
|
|
||||||
[2, 2],
|
|
||||||
[2, 3],
|
|
||||||
])("should pick the first provider of %d success(es) after %d failure(s)", async (successes, failures) => {
|
|
||||||
runInAction(() => {
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-failure-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < successes; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-success-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const details = await di.inject(prometheusHandlerInjectable, cluster).getPrometheusDetails();
|
|
||||||
|
|
||||||
expect(details.provider.kind === `id_failure_${failures}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[1, 0],
|
|
||||||
[1, 1],
|
|
||||||
[1, 2],
|
|
||||||
[1, 3],
|
|
||||||
[2, 0],
|
|
||||||
[2, 1],
|
|
||||||
[2, 2],
|
|
||||||
[2, 3],
|
|
||||||
])("should pick the first provider of %d success(es) before %d failure(s)", async (successes, failures) => {
|
|
||||||
runInAction(() => {
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-failure-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < successes; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-success-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const details = await di.inject(prometheusHandlerInjectable, cluster).getPrometheusDetails();
|
|
||||||
|
|
||||||
expect(details.provider.kind === "id_failure_0");
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
[1, 0],
|
|
||||||
[1, 1],
|
|
||||||
[1, 2],
|
|
||||||
[1, 3],
|
|
||||||
[2, 0],
|
|
||||||
[2, 1],
|
|
||||||
[2, 2],
|
|
||||||
[2, 3],
|
|
||||||
])("should pick the first provider of %d success(es) between %d failure(s)", async (successes, failures) => {
|
|
||||||
const beforeSuccesses = Math.floor(successes / 2);
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
for (let i = 0; i < beforeSuccesses; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-success-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < failures; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-failure-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_failure_${i}`, ServiceResult.Failure),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = beforeSuccesses; i < successes; i += 1) {
|
|
||||||
di.register(getInjectable({
|
|
||||||
id: `test-prometheus-provider-success-${i}`,
|
|
||||||
injectionToken: prometheusProviderInjectionToken,
|
|
||||||
instantiate: () => createTestPrometheusProvider(`id_success_${i}`, ServiceResult.Success),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const details = await di.inject(prometheusHandlerInjectable, cluster).getPrometheusDetails();
|
|
||||||
|
|
||||||
expect(details.provider.kind === "id_success_0");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable";
|
import waitUntilPortIsUsedInjectable from "../kube-auth-proxy/wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||||
import { Cluster } from "../../common/cluster/cluster";
|
import { Cluster } from "../../common/cluster/cluster";
|
||||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
|
||||||
import type { ChildProcess } from "child_process";
|
import type { ChildProcess } from "child_process";
|
||||||
import { Kubectl } from "../kubectl/kubectl";
|
import { Kubectl } from "../kubectl/kubectl";
|
||||||
import type { DeepMockProxy } from "jest-mock-extended";
|
import type { DeepMockProxy } from "jest-mock-extended";
|
||||||
@ -13,6 +12,7 @@ import { mockDeep, mock } from "jest-mock-extended";
|
|||||||
import type { Readable } from "stream";
|
import type { Readable } from "stream";
|
||||||
import { EventEmitter } from "stream";
|
import { EventEmitter } from "stream";
|
||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
|
import type { CreateKubeAuthProxy, KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
@ -29,7 +29,7 @@ import getBasenameOfPathInjectable from "../../common/path/get-basename.injectab
|
|||||||
const clusterServerUrl = "https://192.168.64.3:8443";
|
const clusterServerUrl = "https://192.168.64.3:8443";
|
||||||
|
|
||||||
describe("kube auth proxy tests", () => {
|
describe("kube auth proxy tests", () => {
|
||||||
let createKubeAuthProxy: (cluster: Cluster, environmentVariables: NodeJS.ProcessEnv) => KubeAuthProxy;
|
let createKubeAuthProxy: CreateKubeAuthProxy;
|
||||||
let spawnMock: jest.Mock;
|
let spawnMock: jest.Mock;
|
||||||
let waitUntilPortIsUsedMock: jest.Mock;
|
let waitUntilPortIsUsedMock: jest.Mock;
|
||||||
let broadcastMessageMock: jest.Mock;
|
let broadcastMessageMock: jest.Mock;
|
||||||
|
|||||||
@ -43,7 +43,7 @@ const createTestPrometheusProvider = (kind: string, alwaysFail: ServiceResult):
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ContextHandler", () => {
|
describe("PrometheusHandler", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let cluster: Cluster;
|
let cluster: Cluster;
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,7 @@ const setupAppPathsInjectable = getInjectable({
|
|||||||
const appDataPath = getElectronAppPath("appData");
|
const appDataPath = getElectronAppPath("appData");
|
||||||
|
|
||||||
setElectronAppPath("userData", joinPaths(appDataPath, appName));
|
setElectronAppPath("userData", joinPaths(appDataPath, appName));
|
||||||
|
setElectronAppPath("sessionData", getElectronAppPath("userData"));
|
||||||
|
|
||||||
const appPaths = pipeline(
|
const appPaths = pipeline(
|
||||||
pathNames,
|
pathNames,
|
||||||
|
|||||||
@ -15,8 +15,11 @@ export interface ClusterData {
|
|||||||
readonly id: string;
|
readonly id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RequestApiVersions = (cluster: ClusterData) => AsyncResult<KubeResourceListGroup[], Error>;
|
export interface ApiVersionsRequester {
|
||||||
|
request(cluster: ClusterData): AsyncResult<KubeResourceListGroup[], Error>;
|
||||||
|
readonly orderNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const requestApiVersionsInjectionToken = getInjectionToken<RequestApiVersions>({
|
export const apiVersionsRequesterInjectionToken = getInjectionToken<ApiVersionsRequester>({
|
||||||
id: "request-api-versions-token",
|
id: "request-api-versions-token",
|
||||||
});
|
});
|
||||||
@ -6,10 +6,7 @@
|
|||||||
import { type KubeConfig, HttpError } from "@kubernetes/client-node";
|
import { type KubeConfig, HttpError } from "@kubernetes/client-node";
|
||||||
import { reaction, comparer, runInAction } from "mobx";
|
import { reaction, comparer, runInAction } from "mobx";
|
||||||
import { ClusterStatus } from "../../common/cluster-types";
|
import { ClusterStatus } from "../../common/cluster-types";
|
||||||
import type { CreateAuthorizationReview } from "../../common/cluster/authorization-review.injectable";
|
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
|
||||||
import type { CreateListNamespaces } from "../../common/cluster/list-namespaces.injectable";
|
import type { CreateListNamespaces } from "../../common/cluster/list-namespaces.injectable";
|
||||||
import type { RequestNamespaceListPermissionsFor, RequestNamespaceListPermissions } from "../../common/cluster/request-namespace-list-permissions.injectable";
|
|
||||||
import type { BroadcastMessage } from "../../common/ipc/broadcast-message.injectable";
|
import type { BroadcastMessage } from "../../common/ipc/broadcast-message.injectable";
|
||||||
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
|
import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster";
|
||||||
import type { Logger } from "../../common/logger";
|
import type { Logger } from "../../common/logger";
|
||||||
@ -25,7 +22,6 @@ import type { RequestApiResources } from "./request-api-resources.injectable";
|
|||||||
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
||||||
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
|
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
|
||||||
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
import broadcastMessageInjectable from "../../common/ipc/broadcast-message.injectable";
|
||||||
import createAuthorizationReviewInjectable from "../../common/cluster/authorization-review.injectable";
|
|
||||||
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
import createListNamespacesInjectable from "../../common/cluster/list-namespaces.injectable";
|
||||||
import kubeAuthProxyServerInjectable from "./kube-auth-proxy-server.injectable";
|
import kubeAuthProxyServerInjectable from "./kube-auth-proxy-server.injectable";
|
||||||
import loadProxyKubeconfigInjectable from "./load-proxy-kubeconfig.injectable";
|
import loadProxyKubeconfigInjectable from "./load-proxy-kubeconfig.injectable";
|
||||||
@ -33,21 +29,31 @@ import loggerInjectable from "../../common/logger.injectable";
|
|||||||
import prometheusHandlerInjectable from "./prometheus-handler/prometheus-handler.injectable";
|
import prometheusHandlerInjectable from "./prometheus-handler/prometheus-handler.injectable";
|
||||||
import removeProxyKubeconfigInjectable from "./remove-proxy-kubeconfig.injectable";
|
import removeProxyKubeconfigInjectable from "./remove-proxy-kubeconfig.injectable";
|
||||||
import requestApiResourcesInjectable from "./request-api-resources.injectable";
|
import requestApiResourcesInjectable from "./request-api-resources.injectable";
|
||||||
import requestNamespaceListPermissionsForInjectable from "../../common/cluster/request-namespace-list-permissions.injectable";
|
|
||||||
import type { DetectClusterMetadata } from "../cluster-detectors/detect-cluster-metadata.injectable";
|
import type { DetectClusterMetadata } from "../cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
import type { FallibleOnlyClusterMetadataDetector } from "../cluster-detectors/token";
|
import type { FallibleOnlyClusterMetadataDetector } from "../cluster-detectors/token";
|
||||||
import clusterVersionDetectorInjectable from "../cluster-detectors/cluster-version-detector.injectable";
|
import clusterVersionDetectorInjectable from "../cluster-detectors/cluster-version-detector.injectable";
|
||||||
import detectClusterMetadataInjectable from "../cluster-detectors/detect-cluster-metadata.injectable";
|
import detectClusterMetadataInjectable from "../cluster-detectors/detect-cluster-metadata.injectable";
|
||||||
import { replaceObservableObject } from "../../common/utils/replace-observable-object";
|
import { replaceObservableObject } from "../../common/utils/replace-observable-object";
|
||||||
|
import type { CreateAuthorizationApi } from "../../common/cluster/create-authorization-api.injectable";
|
||||||
|
import type { CreateCanI } from "../../common/cluster/create-can-i.injectable";
|
||||||
|
import type { CreateRequestNamespaceListPermissions, RequestNamespaceListPermissions } from "../../common/cluster/create-request-namespace-list-permissions.injectable";
|
||||||
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
|
import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable";
|
||||||
|
import createCanIInjectable from "../../common/cluster/create-can-i.injectable";
|
||||||
|
import createRequestNamespaceListPermissionsInjectable from "../../common/cluster/create-request-namespace-list-permissions.injectable";
|
||||||
|
import type { CreateCoreApi } from "../../common/cluster/create-core-api.injectable";
|
||||||
|
import createCoreApiInjectable from "../../common/cluster/create-core-api.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
readonly logger: Logger;
|
readonly logger: Logger;
|
||||||
readonly prometheusHandler: ClusterPrometheusHandler;
|
readonly prometheusHandler: ClusterPrometheusHandler;
|
||||||
readonly kubeAuthProxyServer: KubeAuthProxyServer;
|
readonly kubeAuthProxyServer: KubeAuthProxyServer;
|
||||||
readonly clusterVersionDetector: FallibleOnlyClusterMetadataDetector;
|
readonly clusterVersionDetector: FallibleOnlyClusterMetadataDetector;
|
||||||
createAuthorizationReview: CreateAuthorizationReview;
|
createCanI: CreateCanI;
|
||||||
requestApiResources: RequestApiResources;
|
requestApiResources: RequestApiResources;
|
||||||
requestNamespaceListPermissionsFor: RequestNamespaceListPermissionsFor;
|
createRequestNamespaceListPermissions: CreateRequestNamespaceListPermissions;
|
||||||
|
createAuthorizationApi: CreateAuthorizationApi;
|
||||||
|
createCoreApi: CreateCoreApi;
|
||||||
createListNamespaces: CreateListNamespaces;
|
createListNamespaces: CreateListNamespaces;
|
||||||
detectClusterMetadata: DetectClusterMetadata;
|
detectClusterMetadata: DetectClusterMetadata;
|
||||||
broadcastMessage: BroadcastMessage;
|
broadcastMessage: BroadcastMessage;
|
||||||
@ -224,8 +230,9 @@ class ClusterConnection {
|
|||||||
private async refreshAccessibility(): Promise<void> {
|
private async refreshAccessibility(): Promise<void> {
|
||||||
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.cluster.getMeta());
|
this.dependencies.logger.info(`[CLUSTER]: refreshAccessibility`, this.cluster.getMeta());
|
||||||
const proxyConfig = await this.dependencies.loadProxyKubeconfig();
|
const proxyConfig = await this.dependencies.loadProxyKubeconfig();
|
||||||
const canI = this.dependencies.createAuthorizationReview(proxyConfig);
|
const api = this.dependencies.createAuthorizationApi(proxyConfig);
|
||||||
const requestNamespaceListPermissions = this.dependencies.requestNamespaceListPermissionsFor(proxyConfig);
|
const canI = this.dependencies.createCanI(api);
|
||||||
|
const requestNamespaceListPermissions = this.dependencies.createRequestNamespaceListPermissions(api);
|
||||||
|
|
||||||
const isAdmin = await canI({
|
const isAdmin = await canI({
|
||||||
namespace: "kube-system",
|
namespace: "kube-system",
|
||||||
@ -360,7 +367,8 @@ class ClusterConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const listNamespaces = this.dependencies.createListNamespaces(proxyConfig);
|
const api = this.dependencies.createCoreApi(proxyConfig);
|
||||||
|
const listNamespaces = this.dependencies.createListNamespaces(api);
|
||||||
|
|
||||||
return await listNamespaces();
|
return await listNamespaces();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -403,13 +411,15 @@ const clusterConnectionInjectable = getInjectable({
|
|||||||
prometheusHandler: di.inject(prometheusHandlerInjectable, cluster),
|
prometheusHandler: di.inject(prometheusHandlerInjectable, cluster),
|
||||||
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
|
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
|
||||||
broadcastMessage: di.inject(broadcastMessageInjectable),
|
broadcastMessage: di.inject(broadcastMessageInjectable),
|
||||||
createAuthorizationReview: di.inject(createAuthorizationReviewInjectable),
|
|
||||||
createListNamespaces: di.inject(createListNamespacesInjectable),
|
createListNamespaces: di.inject(createListNamespacesInjectable),
|
||||||
detectClusterMetadata: di.inject(detectClusterMetadataInjectable),
|
detectClusterMetadata: di.inject(detectClusterMetadataInjectable),
|
||||||
loadProxyKubeconfig: di.inject(loadProxyKubeconfigInjectable, cluster),
|
loadProxyKubeconfig: di.inject(loadProxyKubeconfigInjectable, cluster),
|
||||||
removeProxyKubeconfig: di.inject(removeProxyKubeconfigInjectable, cluster),
|
removeProxyKubeconfig: di.inject(removeProxyKubeconfigInjectable, cluster),
|
||||||
requestApiResources: di.inject(requestApiResourcesInjectable),
|
requestApiResources: di.inject(requestApiResourcesInjectable),
|
||||||
requestNamespaceListPermissionsFor: di.inject(requestNamespaceListPermissionsForInjectable),
|
createAuthorizationApi: di.inject(createAuthorizationApiInjectable),
|
||||||
|
createCoreApi: di.inject(createCoreApiInjectable),
|
||||||
|
createCanI: di.inject(createCanIInjectable),
|
||||||
|
createRequestNamespaceListPermissions: di.inject(createRequestNamespaceListPermissionsInjectable),
|
||||||
},
|
},
|
||||||
cluster,
|
cluster,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable";
|
|||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
import createKubeAuthProxyInjectable from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable";
|
import kubeAuthProxyCertificateInjectable from "../kube-auth-proxy/kube-auth-proxy-certificate.injectable";
|
||||||
import type { KubeAuthProxy } from "../kube-auth-proxy/kube-auth-proxy";
|
import type { KubeAuthProxy } from "../kube-auth-proxy/create-kube-auth-proxy.injectable";
|
||||||
|
|
||||||
export interface KubeAuthProxyServer {
|
export interface KubeAuthProxyServer {
|
||||||
getApiTarget(isLongRunningRequest?: boolean): Promise<ServerOptions>;
|
getApiTarget(isLongRunningRequest?: boolean): Promise<ServerOptions>;
|
||||||
|
|||||||
@ -7,11 +7,12 @@ import { getInjectable } from "@ogre-tools/injectable";
|
|||||||
import loggerInjectable from "../../common/logger.injectable";
|
import loggerInjectable from "../../common/logger.injectable";
|
||||||
import type { KubeApiResource } from "../../common/rbac";
|
import type { KubeApiResource } from "../../common/rbac";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
|
||||||
import { backoffCaller, withConcurrencyLimit } from "@k8slens/utilities";
|
import { backoffCaller, withConcurrencyLimit } from "@k8slens/utilities";
|
||||||
import requestKubeApiResourcesForInjectable from "./request-kube-api-resources-for.injectable";
|
import requestKubeApiResourcesForInjectable from "./request-kube-api-resources-for.injectable";
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { AsyncResult } from "@k8slens/utilities";
|
||||||
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
|
import broadcastConnectionUpdateInjectable from "./broadcast-connection-update.injectable";
|
||||||
|
import { byOrderNumber } from "../../common/utils/composable-responsibilities/orderable/orderable";
|
||||||
|
|
||||||
export type RequestApiResources = (cluster: Cluster) => AsyncResult<KubeApiResource[], Error>;
|
export type RequestApiResources = (cluster: Cluster) => AsyncResult<KubeApiResource[], Error>;
|
||||||
|
|
||||||
@ -24,7 +25,8 @@ const requestApiResourcesInjectable = getInjectable({
|
|||||||
id: "request-api-resources",
|
id: "request-api-resources",
|
||||||
instantiate: (di): RequestApiResources => {
|
instantiate: (di): RequestApiResources => {
|
||||||
const logger = di.inject(loggerInjectable);
|
const logger = di.inject(loggerInjectable);
|
||||||
const apiVersionRequesters = di.injectMany(requestApiVersionsInjectionToken);
|
const apiVersionRequesters = di.injectMany(apiVersionsRequesterInjectionToken)
|
||||||
|
.sort(byOrderNumber);
|
||||||
const requestKubeApiResourcesFor = di.inject(requestKubeApiResourcesForInjectable);
|
const requestKubeApiResourcesFor = di.inject(requestKubeApiResourcesForInjectable);
|
||||||
|
|
||||||
return async (...args) => {
|
return async (...args) => {
|
||||||
@ -35,7 +37,7 @@ const requestApiResourcesInjectable = getInjectable({
|
|||||||
const groupLists: KubeResourceListGroup[] = [];
|
const groupLists: KubeResourceListGroup[] = [];
|
||||||
|
|
||||||
for (const apiVersionRequester of apiVersionRequesters) {
|
for (const apiVersionRequester of apiVersionRequesters) {
|
||||||
const result = await backoffCaller(() => apiVersionRequester(cluster), {
|
const result = await backoffCaller(() => apiVersionRequester.request(cluster), {
|
||||||
onIntermediateError: (error, attempt) => {
|
onIntermediateError: (error, attempt) => {
|
||||||
broadcastConnectionUpdate({
|
broadcastConnectionUpdate({
|
||||||
message: `Failed to list kube API resource kinds, attempt ${attempt}: ${error}`,
|
message: `Failed to list kube API resource kinds, attempt ${attempt}: ${error}`,
|
||||||
|
|||||||
@ -5,33 +5,36 @@
|
|||||||
import type { V1APIVersions } from "@kubernetes/client-node";
|
import type { V1APIVersions } from "@kubernetes/client-node";
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
|
||||||
|
|
||||||
const requestCoreApiVersionsInjectable = getInjectable({
|
const requestCoreApiVersionsInjectable = getInjectable({
|
||||||
id: "request-core-api-versions",
|
id: "request-core-api-versions",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const k8sRequest = di.inject(k8sRequestInjectable);
|
const k8sRequest = di.inject(k8sRequestInjectable);
|
||||||
|
|
||||||
return async (cluster) => {
|
return {
|
||||||
try {
|
request: async (cluster) => {
|
||||||
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
|
try {
|
||||||
|
const { versions } = await k8sRequest(cluster, "/api") as V1APIVersions;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callWasSuccessful: true,
|
callWasSuccessful: true,
|
||||||
response: versions.map(version => ({
|
response: versions.map(version => ({
|
||||||
group: "",
|
group: "",
|
||||||
path: `/api/${version}`,
|
path: `/api/${version}`,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
callWasSuccessful: false,
|
callWasSuccessful: false,
|
||||||
error: error as Error,
|
error: error as Error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
orderNumber: 10,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
injectionToken: requestApiVersionsInjectionToken,
|
injectionToken: apiVersionsRequesterInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default requestCoreApiVersionsInjectable;
|
export default requestCoreApiVersionsInjectable;
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type { Cluster } from "../../common/cluster/cluster";
|
|||||||
import type { KubeApiResource } from "../../common/rbac";
|
import type { KubeApiResource } from "../../common/rbac";
|
||||||
import type { AsyncResult } from "@k8slens/utilities";
|
import type { AsyncResult } from "@k8slens/utilities";
|
||||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||||
import type { KubeResourceListGroup } from "./request-api-versions";
|
import type { KubeResourceListGroup } from "./api-versions-requester";
|
||||||
|
|
||||||
export type RequestKubeApiResources = (grouping: KubeResourceListGroup) => AsyncResult<KubeApiResource[], Error>;
|
export type RequestKubeApiResources = (grouping: KubeResourceListGroup) => AsyncResult<KubeApiResource[], Error>;
|
||||||
|
|
||||||
|
|||||||
@ -6,35 +6,40 @@ import type { V1APIGroupList } from "@kubernetes/client-node";
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import { iter } from "@k8slens/utilities";
|
import { iter } from "@k8slens/utilities";
|
||||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||||
import { requestApiVersionsInjectionToken } from "./request-api-versions";
|
import { apiVersionsRequesterInjectionToken } from "./api-versions-requester";
|
||||||
|
|
||||||
const requestNonCoreApiVersionsInjectable = getInjectable({
|
const requestNonCoreApiVersionsInjectable = getInjectable({
|
||||||
id: "request-non-core-api-versions",
|
id: "request-non-core-api-versions",
|
||||||
instantiate: (di) => {
|
instantiate: (di) => {
|
||||||
const k8sRequest = di.inject(k8sRequestInjectable);
|
const k8sRequest = di.inject(k8sRequestInjectable);
|
||||||
|
|
||||||
return async (cluster) => {
|
return {
|
||||||
try {
|
request: async (cluster) => {
|
||||||
const { groups } = await k8sRequest(cluster, "/apis") as V1APIGroupList;
|
try {
|
||||||
|
const { groups } = (await k8sRequest(cluster, "/apis")) as V1APIGroupList;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
callWasSuccessful: true,
|
callWasSuccessful: true,
|
||||||
response: iter.chain(groups.values())
|
response: iter.chain(groups.values())
|
||||||
.flatMap(group => group.versions.map(version => ({
|
.flatMap((group) =>
|
||||||
group: group.name,
|
group.versions.map((version) => ({
|
||||||
path: `/apis/${version.groupVersion}`,
|
group: group.name,
|
||||||
})))
|
path: `/apis/${version.groupVersion}`,
|
||||||
.collect(v => [...v]),
|
})),
|
||||||
};
|
)
|
||||||
} catch (error) {
|
.collect((v) => [...v]),
|
||||||
return {
|
};
|
||||||
callWasSuccessful: false,
|
} catch (error) {
|
||||||
error: error as Error,
|
return {
|
||||||
};
|
callWasSuccessful: false,
|
||||||
}
|
error: error as Error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderNumber: 20,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
injectionToken: requestApiVersionsInjectionToken,
|
injectionToken: apiVersionsRequesterInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default requestNonCoreApiVersionsInjectable;
|
export default requestNonCoreApiVersionsInjectable;
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import type { DiContainer } from "@ogre-tools/injectable";
|
|||||||
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../getDiForUnitTesting";
|
||||||
import type { K8sRequest } from "../k8s-request.injectable";
|
import type { K8sRequest } from "../k8s-request.injectable";
|
||||||
import k8sRequestInjectable from "../k8s-request.injectable";
|
import k8sRequestInjectable from "../k8s-request.injectable";
|
||||||
import type { RequestApiVersions } from "./request-api-versions";
|
import type { ApiVersionsRequester } from "./api-versions-requester";
|
||||||
import requestNonCoreApiVersionsInjectable from "./request-non-core-api-versions.injectable";
|
import requestNonCoreApiVersionsInjectable from "./request-non-core-api-versions.injectable";
|
||||||
|
|
||||||
describe("requestNonCoreApiVersions", () => {
|
describe("requestNonCoreApiVersions", () => {
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
let k8sRequestMock: AsyncFnMock<K8sRequest>;
|
let k8sRequestMock: AsyncFnMock<K8sRequest>;
|
||||||
let requestNonCoreApiVersions: RequestApiVersions;
|
let requestNonCoreApiVersions: ApiVersionsRequester;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
di = getDiForUnitTesting();
|
di = getDiForUnitTesting();
|
||||||
@ -28,10 +28,10 @@ describe("requestNonCoreApiVersions", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("when called", () => {
|
describe("when called", () => {
|
||||||
let versionsRequest: ReturnType<RequestApiVersions>;
|
let versionsRequest: ReturnType<ApiVersionsRequester["request"]>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
versionsRequest = requestNonCoreApiVersions({ id: "some-cluster-id" });
|
versionsRequest = requestNonCoreApiVersions.request({ id: "some-cluster-id" });
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should request all api groups", () => {
|
it("should request all api groups", () => {
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
import type { KubeAuthProxyDependencies } from "./kube-auth-proxy";
|
||||||
import { KubeAuthProxy } from "./kube-auth-proxy";
|
import { KubeAuthProxyImpl } from "./kube-auth-proxy";
|
||||||
import type { Cluster } from "../../common/cluster/cluster";
|
import type { Cluster } from "../../common/cluster/cluster";
|
||||||
import spawnInjectable from "../child-process/spawn.injectable";
|
import spawnInjectable from "../child-process/spawn.injectable";
|
||||||
import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable";
|
import kubeAuthProxyCertificateInjectable from "./kube-auth-proxy-certificate.injectable";
|
||||||
@ -15,6 +15,13 @@ import getPortFromStreamInjectable from "../utils/get-port-from-stream.injectabl
|
|||||||
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable";
|
||||||
import broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable";
|
import broadcastConnectionUpdateInjectable from "../cluster/broadcast-connection-update.injectable";
|
||||||
|
|
||||||
|
export interface KubeAuthProxy {
|
||||||
|
readonly apiPrefix: string;
|
||||||
|
readonly port: number;
|
||||||
|
run: () => Promise<void>;
|
||||||
|
exit: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export type CreateKubeAuthProxy = (cluster: Cluster, env: NodeJS.ProcessEnv) => KubeAuthProxy;
|
export type CreateKubeAuthProxy = (cluster: Cluster, env: NodeJS.ProcessEnv) => KubeAuthProxy;
|
||||||
|
|
||||||
const createKubeAuthProxyInjectable = getInjectable({
|
const createKubeAuthProxyInjectable = getInjectable({
|
||||||
@ -33,7 +40,7 @@ const createKubeAuthProxyInjectable = getInjectable({
|
|||||||
return (cluster, env) => {
|
return (cluster, env) => {
|
||||||
const clusterUrl = new URL(cluster.apiUrl.get());
|
const clusterUrl = new URL(cluster.apiUrl.get());
|
||||||
|
|
||||||
return new KubeAuthProxy({
|
return new KubeAuthProxyImpl({
|
||||||
...dependencies,
|
...dependencies,
|
||||||
proxyCert: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname),
|
proxyCert: di.inject(kubeAuthProxyCertificateInjectable, clusterUrl.hostname),
|
||||||
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
|
broadcastConnectionUpdate: di.inject(broadcastConnectionUpdateInjectable, cluster),
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import type { Logger } from "../../common/logger";
|
|||||||
import type { WaitUntilPortIsUsed } from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
import type { WaitUntilPortIsUsed } from "./wait-until-port-is-used/wait-until-port-is-used.injectable";
|
||||||
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable";
|
||||||
import type { BroadcastConnectionUpdate } from "../cluster/broadcast-connection-update.injectable";
|
import type { BroadcastConnectionUpdate } from "../cluster/broadcast-connection-update.injectable";
|
||||||
|
import type { KubeAuthProxy } from "./create-kube-auth-proxy.injectable";
|
||||||
|
|
||||||
const startingServeMatcher = "starting to serve on (?<address>.+)";
|
const startingServeMatcher = "starting to serve on (?<address>.+)";
|
||||||
const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), {
|
const startingServeRegex = Object.assign(TypedRegEx(startingServeMatcher, "i"), {
|
||||||
@ -33,7 +34,7 @@ export interface KubeAuthProxyDependencies {
|
|||||||
broadcastConnectionUpdate: BroadcastConnectionUpdate;
|
broadcastConnectionUpdate: BroadcastConnectionUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KubeAuthProxy {
|
export class KubeAuthProxyImpl implements KubeAuthProxy {
|
||||||
public readonly apiPrefix = `/${randomBytes(8).toString("hex")}`;
|
public readonly apiPrefix = `/${randomBytes(8).toString("hex")}`;
|
||||||
|
|
||||||
public get port(): number {
|
public get port(): number {
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export class KubeconfigManager {
|
|||||||
|
|
||||||
return this.tempFilePath = await this.createProxyKubeconfig();
|
return this.tempFilePath = await this.createProxyKubeconfig();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to creat temp config for auth-proxy: ${error}`);
|
throw new Error(`Failed to create temp config for auth-proxy: ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,11 @@ import { BrowserWindow } from "electron";
|
|||||||
const resolveSystemProxyWindowInjectable = getInjectable({
|
const resolveSystemProxyWindowInjectable = getInjectable({
|
||||||
id: "resolve-system-proxy-window",
|
id: "resolve-system-proxy-window",
|
||||||
instantiate: () => {
|
instantiate: () => {
|
||||||
return new BrowserWindow({ show: false, paintWhenInitiallyHidden: false });
|
const window = new BrowserWindow({ show: false });
|
||||||
|
|
||||||
|
window.hide();
|
||||||
|
|
||||||
|
return window;
|
||||||
},
|
},
|
||||||
causesSideEffects: true,
|
causesSideEffects: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
/**
|
||||||
|
* 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 { reaction } from "mobx";
|
||||||
|
import { customResourceDefinitionApiInjectionToken } from "../../../common/k8s-api/api-manager/crd-api-token";
|
||||||
|
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
||||||
|
import { KubeApi } from "../../../common/k8s-api/kube-api";
|
||||||
|
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
|
import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable";
|
||||||
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
|
import { injectableDifferencingRegistratorWith } from "../../../common/utils/registrator-helper";
|
||||||
|
import customResourceDefinitionStoreInjectable from "../../components/+custom-resources/definition.store.injectable";
|
||||||
|
import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens";
|
||||||
|
|
||||||
|
const setupAutoCrdApiCreationsInjectable = getInjectable({
|
||||||
|
id: "setup-auto-crd-api-creations",
|
||||||
|
instantiate: (di) => ({
|
||||||
|
run: () => {
|
||||||
|
const customResourceDefinitionStore = di.inject(customResourceDefinitionStoreInjectable);
|
||||||
|
const injectableDifferencingRegistrator = injectableDifferencingRegistratorWith(di);
|
||||||
|
|
||||||
|
reaction(
|
||||||
|
() => customResourceDefinitionStore.getItems().map(toCrdApiInjectable),
|
||||||
|
injectableDifferencingRegistrator,
|
||||||
|
{
|
||||||
|
fireImmediately: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
injectionToken: beforeClusterFrameStartsSecondInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default setupAutoCrdApiCreationsInjectable;
|
||||||
|
|
||||||
|
const toCrdApiInjectable = (crd: CustomResourceDefinition) => getInjectable({
|
||||||
|
id: `default-kube-api-for-custom-resource-definition-${crd.getResourceApiBase()}`,
|
||||||
|
instantiate: (di) => {
|
||||||
|
const objectConstructor = class extends KubeObject {
|
||||||
|
static readonly kind = crd.getResourceKind();
|
||||||
|
static readonly namespaced = crd.isNamespaced();
|
||||||
|
static readonly apiBase = crd.getResourceApiBase();
|
||||||
|
};
|
||||||
|
|
||||||
|
return new KubeApi({
|
||||||
|
logger: di.inject(loggerInjectable),
|
||||||
|
maybeKubeApi: di.inject(maybeKubeApiInjectable),
|
||||||
|
}, { objectConstructor });
|
||||||
|
},
|
||||||
|
injectionToken: customResourceDefinitionApiInjectionToken,
|
||||||
|
});
|
||||||
@ -5,71 +5,22 @@
|
|||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
|
import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
|
||||||
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
import apiManagerInjectable from "../../../common/k8s-api/api-manager/manager.injectable";
|
||||||
import { CustomResourceStore } from "../../../common/k8s-api/api-manager/resource.store";
|
import type { KubeApi } from "../../../common/k8s-api/kube-api";
|
||||||
import type { CustomResourceDefinition } from "../../../common/k8s-api/endpoints";
|
|
||||||
import type { KubeApiDependencies } from "../../../common/k8s-api/kube-api";
|
|
||||||
import { KubeApi } from "../../../common/k8s-api/kube-api";
|
|
||||||
import { KubeObject } from "../../../common/k8s-api/kube-object";
|
|
||||||
import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens";
|
import { beforeClusterFrameStartsSecondInjectionToken } from "../tokens";
|
||||||
import type { KubeObjectStoreDependencies } from "../../../common/k8s-api/kube-object.store";
|
|
||||||
import clusterFrameContextForNamespacedResourcesInjectable from "../../cluster-frame-context/for-namespaced-resources.injectable";
|
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
|
||||||
import maybeKubeApiInjectable from "../../../common/k8s-api/maybe-kube-api.injectable";
|
|
||||||
|
|
||||||
const setupAutoRegistrationInjectable = getInjectable({
|
const setupAutoRegistrationInjectable = getInjectable({
|
||||||
id: "setup-auto-registration",
|
id: "setup-auto-registration",
|
||||||
instantiate: (di) => ({
|
instantiate: (di) => ({
|
||||||
run: () => {
|
run: () => {
|
||||||
const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable);
|
const autoRegistrationEmitter = di.inject(autoRegistrationEmitterInjectable);
|
||||||
const beforeApiManagerInitializationCrds: CustomResourceDefinition[] = [];
|
|
||||||
const beforeApiManagerInitializationApis: KubeApi[] = [];
|
const beforeApiManagerInitializationApis: KubeApi[] = [];
|
||||||
const kubeApiDependencies: KubeApiDependencies = {
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
maybeKubeApi: di.inject(maybeKubeApiInjectable),
|
|
||||||
};
|
|
||||||
const kubeObjectStoreDependencies: KubeObjectStoreDependencies = {
|
|
||||||
context: di.inject(clusterFrameContextForNamespacedResourcesInjectable),
|
|
||||||
logger: di.inject(loggerInjectable),
|
|
||||||
};
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
const autoInitCustomResourceStore = (crd: CustomResourceDefinition) => {
|
|
||||||
const objectConstructor = class extends KubeObject {
|
|
||||||
static readonly kind = crd.getResourceKind();
|
|
||||||
static readonly namespaced = crd.isNamespaced();
|
|
||||||
static readonly apiBase = crd.getResourceApiBase();
|
|
||||||
};
|
|
||||||
|
|
||||||
const api = (() => {
|
|
||||||
const rawApi = apiManager.getApi(objectConstructor.apiBase);
|
|
||||||
|
|
||||||
if (rawApi) {
|
|
||||||
return rawApi;
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = new KubeApi(kubeApiDependencies, { objectConstructor });
|
|
||||||
|
|
||||||
apiManager.registerApi(api);
|
|
||||||
|
|
||||||
return api;
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!apiManager.getStore(api)) {
|
|
||||||
apiManager.registerStore(new CustomResourceStore(kubeObjectStoreDependencies, api));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const autoInitKubeApi = (api: KubeApi) => {
|
const autoInitKubeApi = (api: KubeApi) => {
|
||||||
apiManager.registerApi(api);
|
apiManager.registerApi(api);
|
||||||
};
|
};
|
||||||
|
|
||||||
autoRegistrationEmitter
|
autoRegistrationEmitter
|
||||||
.on("customResourceDefinition", (crd) => {
|
|
||||||
if (initialized) {
|
|
||||||
autoInitCustomResourceStore(crd);
|
|
||||||
} else {
|
|
||||||
beforeApiManagerInitializationCrds.push(crd);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.on("kubeApi", (api) => {
|
.on("kubeApi", (api) => {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
autoInitKubeApi(api);
|
autoInitKubeApi(api);
|
||||||
@ -81,7 +32,6 @@ const setupAutoRegistrationInjectable = getInjectable({
|
|||||||
// NOTE: this MUST happen after the event emitter listeners are registered
|
// NOTE: this MUST happen after the event emitter listeners are registered
|
||||||
const apiManager = di.inject(apiManagerInjectable);
|
const apiManager = di.inject(apiManagerInjectable);
|
||||||
|
|
||||||
beforeApiManagerInitializationCrds.forEach(autoInitCustomResourceStore);
|
|
||||||
beforeApiManagerInitializationApis.forEach(autoInitKubeApi);
|
beforeApiManagerInitializationApis.forEach(autoInitKubeApi);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,8 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
import type { RenderResult } from "@testing-library/react";
|
import type { RenderResult } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable";
|
||||||
|
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||||
|
import { Cluster } from "../../../common/cluster/cluster";
|
||||||
import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints";
|
import { HorizontalPodAutoscaler, HpaMetricType } from "../../../common/k8s-api/endpoints";
|
||||||
|
import hostedClusterInjectable from "../../cluster-frame-context/hosted-cluster.injectable";
|
||||||
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
import { getDiForUnitTesting } from "../../getDiForUnitTesting";
|
||||||
|
import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-created.injectable";
|
||||||
import type { DiRender } from "../test-utils/renderFor";
|
import type { DiRender } from "../test-utils/renderFor";
|
||||||
import { renderFor } from "../test-utils/renderFor";
|
import { renderFor } from "../test-utils/renderFor";
|
||||||
import { HpaDetails } from "./hpa-details";
|
import { HpaDetails } from "./hpa-details";
|
||||||
@ -41,6 +46,17 @@ describe("<HpaDetails/>", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const di = getDiForUnitTesting();
|
const di = getDiForUnitTesting();
|
||||||
|
|
||||||
|
di.override(directoryForUserDataInjectable, () => "/some-user-store-path");
|
||||||
|
di.override(directoryForKubeConfigsInjectable, () => "/some-kube-configs");
|
||||||
|
di.override(storesAndApisCanBeCreatedInjectable, () => true);
|
||||||
|
di.override(hostedClusterInjectable, () => new Cluster({
|
||||||
|
contextName: "some-context-name",
|
||||||
|
id: "some-cluster-id",
|
||||||
|
kubeConfigPath: "/some-path-to-a-kubeconfig",
|
||||||
|
}, {
|
||||||
|
clusterServerUrl: "https://localhost:8080",
|
||||||
|
}));
|
||||||
|
|
||||||
render = renderFor(di);
|
render = renderFor(di);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
import assert from "assert";
|
import assert from "assert";
|
||||||
import autoRegistrationEmitterInjectable from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
|
|
||||||
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/kube-object-store-token";
|
import { kubeObjectStoreInjectionToken } from "../../../common/k8s-api/api-manager/kube-object-store-token";
|
||||||
import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable";
|
import customResourceDefinitionApiInjectable from "../../../common/k8s-api/endpoints/custom-resource-definition.api.injectable";
|
||||||
import loggerInjectable from "../../../common/logger.injectable";
|
import loggerInjectable from "../../../common/logger.injectable";
|
||||||
@ -20,7 +19,6 @@ const customResourceDefinitionStoreInjectable = getInjectable({
|
|||||||
const api = di.inject(customResourceDefinitionApiInjectable);
|
const api = di.inject(customResourceDefinitionApiInjectable);
|
||||||
|
|
||||||
return new CustomResourceDefinitionStore({
|
return new CustomResourceDefinitionStore({
|
||||||
autoRegistration: di.inject(autoRegistrationEmitterInjectable),
|
|
||||||
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
|
context: di.inject(clusterFrameContextForClusterScopedResourcesInjectable),
|
||||||
logger: di.inject(loggerInjectable),
|
logger: di.inject(loggerInjectable),
|
||||||
}, api);
|
}, api);
|
||||||
|
|||||||
@ -3,37 +3,22 @@
|
|||||||
* 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 { computed, reaction, makeObservable } from "mobx";
|
import { computed, makeObservable } from "mobx";
|
||||||
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
|
import type { KubeObjectStoreDependencies, KubeObjectStoreOptions } from "../../../common/k8s-api/kube-object.store";
|
||||||
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
import { KubeObjectStore } from "../../../common/k8s-api/kube-object.store";
|
||||||
import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api";
|
import type { CustomResourceDefinition, CustomResourceDefinitionApi } from "../../../common/k8s-api/endpoints/custom-resource-definition.api";
|
||||||
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
import type { KubeObject } from "../../../common/k8s-api/kube-object";
|
||||||
import type TypedEventEmitter from "typed-emitter";
|
|
||||||
import type { LegacyAutoRegistration } from "../../../common/k8s-api/api-manager/auto-registration-emitter.injectable";
|
|
||||||
import autoBind from "auto-bind";
|
import autoBind from "auto-bind";
|
||||||
|
|
||||||
export interface CustomResourceDefinitionStoreDependencies extends KubeObjectStoreDependencies {
|
|
||||||
readonly autoRegistration: TypedEventEmitter<LegacyAutoRegistration>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CustomResourceDefinitionStore extends KubeObjectStore<CustomResourceDefinition, CustomResourceDefinitionApi> {
|
export class CustomResourceDefinitionStore extends KubeObjectStore<CustomResourceDefinition, CustomResourceDefinitionApi> {
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly dependencies: CustomResourceDefinitionStoreDependencies,
|
dependencies: KubeObjectStoreDependencies,
|
||||||
api: CustomResourceDefinitionApi,
|
api: CustomResourceDefinitionApi,
|
||||||
opts?: KubeObjectStoreOptions,
|
opts?: KubeObjectStoreOptions,
|
||||||
) {
|
) {
|
||||||
super(dependencies, api, opts);
|
super(dependencies, api, opts);
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
|
|
||||||
reaction(
|
|
||||||
() => this.getItems(),
|
|
||||||
crds => {
|
|
||||||
for (const crd of crds) {
|
|
||||||
this.dependencies.autoRegistration.emit("customResourceDefinition", crd);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sortItems(items: CustomResourceDefinition[]) {
|
protected sortItems(items: CustomResourceDefinition[]) {
|
||||||
|
|||||||
@ -51,3 +51,55 @@ exports[`Icon settings given no external registrations for cluster settings menu
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`Icon settings given no registrations for cluster settings component injection token renders 1`] = `
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="file-loader flex flex-row items-center"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mr-5"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="FilePicker"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="flex gaps align-center"
|
||||||
|
for="file-upload"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="Avatar rounded"
|
||||||
|
style="width: 53px; height: 53px; background: rgb(9, 124, 92);"
|
||||||
|
>
|
||||||
|
skc
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
accept="image/*"
|
||||||
|
id="file-upload"
|
||||||
|
name="FilePicker"
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<i
|
||||||
|
class="Icon material interactive focusable"
|
||||||
|
data-testid="icon-for-menu-actions-for-cluster-icon-settings-for-some-entity-id"
|
||||||
|
id="menu-actions-for-cluster-icon-settings-for-some-entity-id"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="icon"
|
||||||
|
data-icon-name="more_horiz"
|
||||||
|
>
|
||||||
|
more_horiz
|
||||||
|
</span>
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
`;
|
||||||
|
|||||||
@ -2,8 +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 { DiContainer } from "@ogre-tools/injectable";
|
|
||||||
import { getInjectable } from "@ogre-tools/injectable";
|
|
||||||
import type { RenderResult } from "@testing-library/react";
|
import type { RenderResult } from "@testing-library/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { KubernetesCluster } from "../../../../common/catalog-entities";
|
import { KubernetesCluster } from "../../../../common/catalog-entities";
|
||||||
@ -13,13 +12,18 @@ import { renderFor } from "../../test-utils/renderFor";
|
|||||||
import { ClusterIconSetting } from "../icon-settings";
|
import { ClusterIconSetting } from "../icon-settings";
|
||||||
import { screen } from "@testing-library/react";
|
import { screen } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { clusterIconSettingsMenuInjectionToken } from "../cluster-settings-menu-injection-token";
|
import type { ClusterIconSettingComponentProps } from "@k8slens/cluster-settings";
|
||||||
|
import { clusterIconSettingsComponentInjectionToken, clusterIconSettingsMenuInjectionToken } from "@k8slens/cluster-settings";
|
||||||
import { runInAction } from "mobx";
|
import { runInAction } from "mobx";
|
||||||
|
import { getInjectable, type DiContainer } from "@ogre-tools/injectable";
|
||||||
|
|
||||||
const cluster = new Cluster({
|
const cluster = new Cluster({
|
||||||
contextName: "some-context",
|
contextName: "some-context",
|
||||||
id: "some-id",
|
id: "some-id",
|
||||||
kubeConfigPath: "/some/path/to/kubeconfig",
|
kubeConfigPath: "/some/path/to/kubeconfig",
|
||||||
|
preferences: {
|
||||||
|
clusterName: "some-cluster-name",
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
clusterServerUrl: "https://localhost:9999",
|
clusterServerUrl: "https://localhost:9999",
|
||||||
});
|
});
|
||||||
@ -53,6 +57,29 @@ const newMenuItem = getInjectable({
|
|||||||
injectionToken: clusterIconSettingsMenuInjectionToken,
|
injectionToken: clusterIconSettingsMenuInjectionToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function CustomSettingsComponent(props: ClusterIconSettingComponentProps) {
|
||||||
|
return (
|
||||||
|
<div data-testid="my-react-component">
|
||||||
|
<span>Test React Component</span>
|
||||||
|
<span>
|
||||||
|
Cluster
|
||||||
|
{props.preferences.clusterName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSettingsReactComponent = getInjectable({
|
||||||
|
id: "cluster-icon-settings-react-component",
|
||||||
|
|
||||||
|
instantiate: () => ({
|
||||||
|
id: "test-react-component",
|
||||||
|
Component: CustomSettingsComponent,
|
||||||
|
}),
|
||||||
|
|
||||||
|
injectionToken: clusterIconSettingsComponentInjectionToken,
|
||||||
|
});
|
||||||
|
|
||||||
describe("Icon settings", () => {
|
describe("Icon settings", () => {
|
||||||
let rendered: RenderResult;
|
let rendered: RenderResult;
|
||||||
let di: DiContainer;
|
let di: DiContainer;
|
||||||
@ -98,4 +125,30 @@ describe("Icon settings", () => {
|
|||||||
expect(rendered.getByText("Hello World")).toBeInTheDocument();
|
expect(rendered.getByText("Hello World")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("given no registrations for cluster settings component injection token", () => {
|
||||||
|
it("renders", () => {
|
||||||
|
expect(rendered.baseElement).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not have any external components", async () => {
|
||||||
|
expect(rendered.queryByTestId("test-react-component")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given registration for cluster settings component injection token", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
runInAction(() => {
|
||||||
|
di.register(newSettingsReactComponent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders external component", async () => {
|
||||||
|
expect(rendered.queryByTestId("my-react-component")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("external component has cluster preferences in props", async () => {
|
||||||
|
expect(rendered.getByText(/some-cluster-name/)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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 { clusterIconSettingsMenuInjectionToken } from "./cluster-settings-menu-injection-token";
|
import { clusterIconSettingsMenuInjectionToken } from "@k8slens/cluster-settings";
|
||||||
|
|
||||||
const clusterIconSettingsMenuClearItem = getInjectable({
|
const clusterIconSettingsMenuClearItem = getInjectable({
|
||||||
id: "cluster-icon-settings-menu-clear-item",
|
id: "cluster-icon-settings-menu-clear-item",
|
||||||
|
|||||||
@ -1,17 +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 { ClusterPreferences } from "../../../common/cluster-types";
|
|
||||||
|
|
||||||
export interface ClusterIconMenuItem {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
disabled?: (preferences: ClusterPreferences) => boolean;
|
|
||||||
onClick: (preferences: ClusterPreferences) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const clusterIconSettingsMenuInjectionToken = getInjectionToken<ClusterIconMenuItem>({
|
|
||||||
id: "cluster-icon-settings-menu-injection-token",
|
|
||||||
});
|
|
||||||
@ -15,8 +15,8 @@ import { FilePicker, OverSizeLimitStyle } from "../file-picker";
|
|||||||
import { MenuActions, MenuItem } from "../menu";
|
import { MenuActions, MenuItem } from "../menu";
|
||||||
import type { ShowNotification } from "../notifications";
|
import type { ShowNotification } from "../notifications";
|
||||||
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
import showErrorNotificationInjectable from "../notifications/show-error-notification.injectable";
|
||||||
import type { ClusterIconMenuItem } from "./cluster-settings-menu-injection-token";
|
import { clusterIconSettingsComponentInjectionToken, clusterIconSettingsMenuInjectionToken } from "@k8slens/cluster-settings";
|
||||||
import { clusterIconSettingsMenuInjectionToken } from "./cluster-settings-menu-injection-token";
|
import type { ClusterIconMenuItem, ClusterIconSettingsComponent } from "@k8slens/cluster-settings";
|
||||||
|
|
||||||
export interface ClusterIconSettingProps {
|
export interface ClusterIconSettingProps {
|
||||||
cluster: Cluster;
|
cluster: Cluster;
|
||||||
@ -25,6 +25,7 @@ export interface ClusterIconSettingProps {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
menuItems: IComputedValue<ClusterIconMenuItem[]>;
|
menuItems: IComputedValue<ClusterIconMenuItem[]>;
|
||||||
|
settingComponents: IComputedValue<ClusterIconSettingsComponent[]>;
|
||||||
showErrorNotification: ShowNotification;
|
showErrorNotification: ShowNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +96,14 @@ const NonInjectedClusterIconSetting = observer((props: ClusterIconSettingProps &
|
|||||||
)}
|
)}
|
||||||
</MenuActions>
|
</MenuActions>
|
||||||
</div>
|
</div>
|
||||||
|
{props.settingComponents.get().map(item => {
|
||||||
|
return (
|
||||||
|
<item.Component
|
||||||
|
key={item.id}
|
||||||
|
preferences={cluster.preferences}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -106,6 +115,7 @@ export const ClusterIconSetting = withInjectables<Dependencies, ClusterIconSetti
|
|||||||
return {
|
return {
|
||||||
...props,
|
...props,
|
||||||
menuItems: computedInjectMany(clusterIconSettingsMenuInjectionToken),
|
menuItems: computedInjectMany(clusterIconSettingsMenuInjectionToken),
|
||||||
|
settingComponents: computedInjectMany(clusterIconSettingsComponentInjectionToken),
|
||||||
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
showErrorNotification: di.inject(showErrorNotificationInjectable),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,7 +53,7 @@ import { applicationWindowInjectionToken } from "../../../main/start-main-applic
|
|||||||
import closeAllWindowsInjectable from "../../../main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
import closeAllWindowsInjectable from "../../../main/start-main-application/lens-window/hide-all-windows/close-all-windows.injectable";
|
||||||
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
|
import type { LensWindow } from "../../../main/start-main-application/lens-window/application-window/create-lens-window.injectable";
|
||||||
import type { FakeExtensionOptions } from "./get-extension-fake";
|
import type { FakeExtensionOptions } from "./get-extension-fake";
|
||||||
import { getExtensionFakeForMain, getExtensionFakeForRenderer } from "./get-extension-fake";
|
import { getMainExtensionFakeWith, getRendererExtensionFakeWith } from "./get-extension-fake";
|
||||||
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
import namespaceApiInjectable from "../../../common/k8s-api/endpoints/namespace.api.injectable";
|
||||||
import { Namespace } from "../../../common/k8s-api/endpoints";
|
import { Namespace } from "../../../common/k8s-api/endpoints";
|
||||||
import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
|
import { getOverrideFsWithFakes } from "../../../test-utils/override-fs-with-fakes";
|
||||||
@ -594,49 +594,28 @@ export const getApplicationBuilder = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
enable: (...extensions) => {
|
enable: (...extensions) => {
|
||||||
builder.afterWindowStart(({ windowDi }) => {
|
builder.afterWindowStart(action(({ windowDi }) => {
|
||||||
const rendererExtensionInstances = extensions.map((options) =>
|
extensions
|
||||||
getExtensionFakeForRenderer(
|
.map(getRendererExtensionFakeWith(windowDi))
|
||||||
windowDi,
|
.forEach(enableExtensionFor(windowDi, rendererExtensionsStateInjectable));
|
||||||
options.id,
|
}));
|
||||||
options.name,
|
|
||||||
options.rendererOptions || {},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
rendererExtensionInstances.forEach(
|
builder.afterApplicationStart(action(({ mainDi }) => {
|
||||||
enableExtensionFor(windowDi, rendererExtensionsStateInjectable),
|
extensions
|
||||||
);
|
.map(getMainExtensionFakeWith(mainDi))
|
||||||
});
|
.forEach(enableExtensionFor(mainDi, mainExtensionsStateInjectable));
|
||||||
|
}));
|
||||||
builder.afterApplicationStart(({ mainDi }) => {
|
|
||||||
const mainExtensionInstances = extensions.map((extension) =>
|
|
||||||
getExtensionFakeForMain(mainDi, extension.id, extension.name, extension.mainOptions || {}),
|
|
||||||
);
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
mainExtensionInstances.forEach(
|
|
||||||
enableExtensionFor(mainDi, mainExtensionsStateInjectable),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
disable: (...extensions) => {
|
disable: (...extensions) => {
|
||||||
builder.afterWindowStart(({ windowDi }) => {
|
builder.afterWindowStart(({ windowDi }) => {
|
||||||
extensions
|
extensions
|
||||||
.map((ext) => ext.id)
|
.forEach(disableExtensionFor(windowDi, rendererExtensionsStateInjectable));
|
||||||
.forEach(
|
|
||||||
disableExtensionFor(windowDi, rendererExtensionsStateInjectable),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.afterApplicationStart(({ mainDi }) => {
|
builder.afterApplicationStart(({ mainDi }) => {
|
||||||
extensions
|
extensions
|
||||||
.map((ext) => ext.id)
|
.forEach(disableExtensionFor(mainDi, mainExtensionsStateInjectable));
|
||||||
.forEach(
|
|
||||||
disableExtensionFor(mainDi, mainExtensionsStateInjectable),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -835,49 +814,29 @@ const selectOptionFor = (builder: ApplicationBuilder, menuId: string) => (labelT
|
|||||||
userEvent.click(option);
|
userEvent.click(option);
|
||||||
};
|
};
|
||||||
|
|
||||||
const enableExtensionFor = (
|
function enableExtensionFor(di: DiContainer, stateInjectable: Injectable<ObservableMap<string, any>, any, any>) {
|
||||||
di: DiContainer,
|
|
||||||
stateInjectable: Injectable<ObservableMap<string, any>, any, any>,
|
|
||||||
) => {
|
|
||||||
const extensionState = di.inject(stateInjectable);
|
const extensionState = di.inject(stateInjectable);
|
||||||
|
|
||||||
const getExtension = (extension: LensExtension) =>
|
return (instance: LensExtension) => {
|
||||||
di.inject(extensionInjectable, extension);
|
const extension = di.inject(extensionInjectable, instance);
|
||||||
|
|
||||||
return (extensionInstance: LensExtension) => {
|
extension.register();
|
||||||
const extension = getExtension(extensionInstance);
|
extensionState.set(instance.id, instance);
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
extension.register();
|
|
||||||
extensionState.set(extensionInstance.id, extensionInstance);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
const disableExtensionFor =
|
function disableExtensionFor(di: DiContainer, stateInjectable: Injectable<ObservableMap<string, any>, unknown, void>) {
|
||||||
(
|
return (extension: FakeExtensionOptions) => {
|
||||||
di: DiContainer,
|
const extensionsState = di.inject(stateInjectable);
|
||||||
stateInjectable: Injectable<ObservableMap<string, any>, unknown, void>,
|
const instance = extensionsState.get(extension.id);
|
||||||
) =>
|
|
||||||
(id: string) => {
|
|
||||||
const getExtension = (extension: LensExtension) =>
|
|
||||||
di.inject(extensionInjectable, extension);
|
|
||||||
|
|
||||||
const extensionsState = di.inject(stateInjectable);
|
if (!instance) {
|
||||||
|
throw new Error(`Tried to disable extension with ID "${extension.id}", but it wasn't enabled`);
|
||||||
|
}
|
||||||
|
|
||||||
const instance = extensionsState.get(id);
|
const injectable = di.inject(extensionInjectable, instance);
|
||||||
|
|
||||||
if (!instance) {
|
injectable.deregister();
|
||||||
throw new Error(
|
extensionsState.delete(extension.id);
|
||||||
`Tried to disable extension with ID "${id}", but it wasn't enabled`,
|
};
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const injectable = getExtension(instance);
|
|
||||||
|
|
||||||
runInAction(() => {
|
|
||||||
injectable.deregister();
|
|
||||||
|
|
||||||
extensionsState.delete(id);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export interface FakeExtensionOptions {
|
|||||||
mainOptions?: Partial<LensMainExtension>;
|
mainOptions?: Partial<LensMainExtension>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getExtensionFakeForMain = (di: DiContainer, id: string, name: string, options: Partial<LensMainExtension>) => {
|
export const getMainExtensionFakeWith = (di: DiContainer) => ({ id, name, mainOptions = {}}: FakeExtensionOptions) => {
|
||||||
const instance = new TestExtensionMain({
|
const instance = new TestExtensionMain({
|
||||||
id,
|
id,
|
||||||
absolutePath: "irrelevant",
|
absolutePath: "irrelevant",
|
||||||
@ -44,7 +44,7 @@ export const getExtensionFakeForMain = (di: DiContainer, id: string, name: strin
|
|||||||
manifestPath: "irrelevant",
|
manifestPath: "irrelevant",
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(instance, options);
|
Object.assign(instance, mainOptions);
|
||||||
|
|
||||||
(instance as Writable<LensMainExtension>)[lensExtensionDependencies] = {
|
(instance as Writable<LensMainExtension>)[lensExtensionDependencies] = {
|
||||||
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
|
fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable),
|
||||||
@ -56,7 +56,7 @@ export const getExtensionFakeForMain = (di: DiContainer, id: string, name: strin
|
|||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: string, options: Partial<LensRendererExtension>) => {
|
export const getRendererExtensionFakeWith = (di: DiContainer) => ({ id, name, rendererOptions = {}}: FakeExtensionOptions) => {
|
||||||
const instance = new TestExtensionRenderer({
|
const instance = new TestExtensionRenderer({
|
||||||
id,
|
id,
|
||||||
absolutePath: "irrelevant",
|
absolutePath: "irrelevant",
|
||||||
@ -73,7 +73,7 @@ export const getExtensionFakeForRenderer = (di: DiContainer, id: string, name: s
|
|||||||
manifestPath: "irrelevant",
|
manifestPath: "irrelevant",
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(instance, options);
|
Object.assign(instance, rendererOptions);
|
||||||
|
|
||||||
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = {
|
(instance as Writable<LensRendererExtension>)[lensExtensionDependencies] = {
|
||||||
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
categoryRegistry: di.inject(catalogCategoryRegistryInjectable),
|
||||||
|
|||||||
6
packages/core/src/test-utils/cast.ts
Normal file
6
packages/core/src/test-utils/cast.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const cast = <T>(data: Partial<T>): T => data as T;
|
||||||
17
packages/core/src/test-utils/mock-interface.ts
Normal file
17
packages/core/src/test-utils/mock-interface.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { AsyncFnMock } from "@async-fn/jest";
|
||||||
|
|
||||||
|
type GetMockedType<T> =
|
||||||
|
T extends (...args: any[]) => Promise<any>
|
||||||
|
? AsyncFnMock<T>
|
||||||
|
: T extends (...args: any[]) => any
|
||||||
|
? jest.MockedFunction<T>
|
||||||
|
: T;
|
||||||
|
|
||||||
|
export type Mocked<T extends object> = {
|
||||||
|
-readonly [P in keyof T]: GetMockedType<T[P]>;
|
||||||
|
};
|
||||||
@ -96,7 +96,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"npmRebuild": false,
|
"npmRebuild": false,
|
||||||
"electronVersion": "19.1.9",
|
"electronVersion": "22.3.3",
|
||||||
"generateUpdatesFilesForAllChannels": true,
|
"generateUpdatesFilesForAllChannels": true,
|
||||||
"files": [
|
"files": [
|
||||||
"static/**/*",
|
"static/**/*",
|
||||||
@ -251,7 +251,7 @@
|
|||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.7.2",
|
"css-loader": "^6.7.2",
|
||||||
"electron": "^19.1.9",
|
"electron": "^22.3.3",
|
||||||
"electron-builder": "^23.6.0",
|
"electron-builder": "^23.6.0",
|
||||||
"electron-notarize": "^0.3.0",
|
"electron-notarize": "^0.3.0",
|
||||||
"esbuild-loader": "^2.20.0",
|
"esbuild-loader": "^2.20.0",
|
||||||
|
|||||||
@ -35,7 +35,7 @@
|
|||||||
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.9"
|
"electron": "^22.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "^1.6.4",
|
"@async-fn/jest": "^1.6.4",
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export interface MessageChannel<Message> {
|
|||||||
export type ExtraData = { processId: number; frameId: number };
|
export type ExtraData = { processId: number; frameId: number };
|
||||||
|
|
||||||
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<infer Message>
|
export type MessageChannelHandler<Channel> = Channel extends MessageChannel<infer Message>
|
||||||
? (message: Message, data: ExtraData) => void
|
? (message: Message, data?: ExtraData) => void
|
||||||
: never;
|
: never;
|
||||||
|
|
||||||
export interface MessageChannelListener<Channel> {
|
export interface MessageChannelListener<Channel> {
|
||||||
|
|||||||
@ -38,7 +38,7 @@
|
|||||||
"@k8slens/messaging": "^1.0.0-alpha.1",
|
"@k8slens/messaging": "^1.0.0-alpha.1",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.8",
|
"electron": "^22.3.3",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
|
"@k8slens/startable-stoppable": "^1.0.0-alpha.1",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"electron": "^19.1.8",
|
"electron": "^22.3.3",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const enlistMessageChannelListenerInjectable = getInjectable({
|
|||||||
const ipcRenderer = di.inject(ipcRendererInjectable);
|
const ipcRenderer = di.inject(ipcRendererInjectable);
|
||||||
|
|
||||||
return ({ channel, handler }) => {
|
return ({ channel, handler }) => {
|
||||||
const nativeCallback = (_: IpcRendererEvent, message: unknown) => {
|
const nativeCallback = (event: IpcRendererEvent, message: unknown) => {
|
||||||
handler(message);
|
handler(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
* 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 { setImmediate } from "timers";
|
import { setImmediate, setTimeout } from "timers/promises";
|
||||||
|
|
||||||
export const flushPromises = () => new Promise(setImmediate);
|
export const flushPromises = async () => {
|
||||||
|
await setImmediate();
|
||||||
|
await setTimeout(5);
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user