diff --git a/package.json b/package.json index 2728d68dea..d68bce842b 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "devDependencies": { "adr": "^1.4.3", "cross-env": "^7.0.3", - "lerna": "^6.4.1", + "lerna": "^6.5.1", "rimraf": "^4.1.2" } } diff --git a/packages/bump-version-for-cron/package.json b/packages/bump-version-for-cron/package.json index 0a2aa43dd0..74b0b46295 100644 --- a/packages/bump-version-for-cron/package.json +++ b/packages/bump-version-for-cron/package.json @@ -23,7 +23,7 @@ }, "devDependencies": { "@swc/cli": "^0.1.61", - "@swc/core": "^1.3.32", + "@swc/core": "^1.3.35", "@types/node": "^16.18.11", "@types/semver": "^7.3.13", "rimraf": "^4.1.2" diff --git a/packages/bump-version-for-cron/yarn.lock b/packages/bump-version-for-cron/yarn.lock index bf80ba0a2b..e1135e04f7 100644 --- a/packages/bump-version-for-cron/yarn.lock +++ b/packages/bump-version-for-cron/yarn.lock @@ -54,71 +54,71 @@ slash "3.0.0" source-map "^0.7.3" -"@swc/core-darwin-arm64@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.34.tgz#1885fec4bd734c840897a68937a52ecab06cffbb" - integrity sha512-m7+kybVLO9uo/TiGBlf/ISmpmm27I/NrFEBGOVBF2xNOs5BY1XHHM6ddbPPckQa38C19nWeAzdJPwGzJw+qO3A== +"@swc/core-darwin-arm64@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.35.tgz#55ff0cc069769ce8bf6562ca0e724fe9c43deb8c" + integrity sha512-zQUFkHx4gZpu0uo2IspvPnKsz8bsdXd5bC33xwjtoAI1cpLerDyqo4v2zIahEp+FdKZjyVsLHtfJiQiA1Qka3A== -"@swc/core-darwin-x64@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.34.tgz#170da8677aeb1b452e6db0050bda049ea3445884" - integrity sha512-arH7LtcOhuC1wy88qgbCO/E5NnBThbxv9HhjScDfoUPRunyvT9whEvSK0eXCXxGvDAiAtXIrW3blIrteOsQaOQ== +"@swc/core-darwin-x64@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.35.tgz#270543a2aad6ddbc2d8e8d9a0b025bc08cef9a48" + integrity sha512-oOSkSGWtALovaw22lNevKD434OQTPf8X+dVPvPMrJXJpJ34dWDlFWpLntoc+arvKLNZ7LQmTuk8rR1hkrAY7cw== -"@swc/core-linux-arm-gnueabihf@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.34.tgz#c31ffd40182f274a473bc43a9699fcfee02cb28d" - integrity sha512-+pvjXsXTBzSxEL3U9869y3Am/3yo6kQfU6VGVAebgLv+pjM+mIHywbgo1Uxw+pgpTuD38BsrtYcaPNeBICN/wA== +"@swc/core-linux-arm-gnueabihf@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.35.tgz#d2c81406202202962c09451ec58e37efacb15c38" + integrity sha512-Yie8k00O6O8BCATS/xeKStquV4OYSskUGRDXBQVDw1FrE23PHaSeHCgg4q6iNZjJzXCOJbaTCKnYoIDn9DMf7A== -"@swc/core-linux-arm64-gnu@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.34.tgz#cfab88840f745979bf07942f0835a8931a817e80" - integrity sha512-hOV1n1j+mDAgp19J+aeAnW4itMTWbaPbSbhEvLsNbVB00LoL6q6pUkWvCi+UbrejV6BIyyE9t7F5fU26SdKR8A== +"@swc/core-linux-arm64-gnu@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.35.tgz#f4670584fbe71525d919fa06db3ad778cee242e6" + integrity sha512-Zlv3WHa/4x2p51HSvjUWXHfSe1Gl2prqImUZJc8NZOlj75BFzVuR0auhQ+LbwvIQ3gaA1LODX9lyS9wXL3yjxA== -"@swc/core-linux-arm64-musl@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.34.tgz#a6e81ed8dcb0897ca14416de90f5f836254a9759" - integrity sha512-r2/Hegp1DRSzG+kg36Tujdn+WX+gO/2wQpVj/g6RPxIPdjy53OOf+UwvJ23Ecn5ZbyJcgKhhTN6H6/ZNHQPqjQ== +"@swc/core-linux-arm64-musl@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.35.tgz#fd116255cca2d8e098637e95f38ae08f95a47db6" + integrity sha512-u6tCYsrSyZ8U+4jLMA/O82veBfLy2aUpn51WxQaeH7wqZGy9TGSJXoO8vWxARQ6b72vjsnKDJHP4MD8hFwcctg== -"@swc/core-linux-x64-gnu@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.34.tgz#0f34f97b4a740767e3f1a484cb64cc492f3d93c0" - integrity sha512-jPxxAo7XlAT7bdIi49PtYN/K1TAxvpVS7otteJLhThOPPTVblIDg63U94ivp3mVQJb3WFH4KNYatEXypyvXppQ== +"@swc/core-linux-x64-gnu@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.35.tgz#7a0fb187f1e9baa38d05273a7576c4eaf80a96b8" + integrity sha512-Dtxf2IbeH7XlNhP1Qt2/MvUPkpEbn7hhGfpSRs4ot8D3Vf5QEX4S/QtC1OsFWuciiYgHAT1Ybjt4xZic9DSkmA== -"@swc/core-linux-x64-musl@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.34.tgz#41f8b5b7a43328f3a24c55f1c12aa9320b387d06" - integrity sha512-eJaUuhvnNtcwpK9Mil4hZTSYZqG519gX5AQQ2VZOhrWBEBJi+jM0RXAvWdESsaXpS7W0CRtbmEXqeUff6UEgpQ== +"@swc/core-linux-x64-musl@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.35.tgz#ad2632b9ae0ea2bfd1461f121b324063c3d6755e" + integrity sha512-4XavNJ60GprjpTiESCu5daJUnmErixPAqDitJSMu4TV32LNIE8G00S9pDLXinDTW1rgcGtQdq1NLkNRmwwovtg== -"@swc/core-win32-arm64-msvc@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.34.tgz#19ba26022b80b98245378dd006f43287d5a840c6" - integrity sha512-KFdeC5bXDcxIQ/1J5Pjj8BOblRFjh89TTJxujxAhKdoD1k0NV9BKEZACja2cTBz0hWD4cYlBX0cESVdL2rkm3w== +"@swc/core-win32-arm64-msvc@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.35.tgz#5761d0f6efd9affa5571104f1a1951b8b530ec45" + integrity sha512-dNGfKCUSX2M4qVyaS80Lyos0FkXyHRCvrdQ2Y4Hrg3FVokiuw3yY6fLohpUfQ5ws3n2A39dh7jGDeh34+l0sGA== -"@swc/core-win32-ia32-msvc@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.34.tgz#82afdb4ee60b84915370d57e4ad506303bd1311d" - integrity sha512-MgWkAQDiWIHfJL5b5aoogenGIt3qcqBSvwLnDQqSWEhkodZjHyCWpQFuaa7Y6ER3pKUIZ5kR8O9aAkDmF39awQ== +"@swc/core-win32-ia32-msvc@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.35.tgz#53ebcf1a6abb0e5152c17da3871d695dfdc07338" + integrity sha512-ChuPSrDR+JBf7S7dEKPicnG8A3bM0uWPsW2vG+V2wH4iNfNxKVemESHosmYVeEZXqMpomNMvLyeHep1rjRsc0Q== -"@swc/core-win32-x64-msvc@1.3.34": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.34.tgz#e14dac980a4e0bd6e1a2313e4d6ea04054aa5871" - integrity sha512-UhaikgVRYBZZdMI7Zo4/eUyYLnjGrC6QAn9aggt1+PiFIM9tXpX8aONUL3LoLkhQhd+6iWygfQ298RRxjKAKuw== +"@swc/core-win32-x64-msvc@1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.35.tgz#186170a7f33d1a08ce00800baf293e6d114659a9" + integrity sha512-/RvphT4WfuGfIK84Ha0dovdPrKB1bW/mc+dtdmhv2E3EGkNc5FoueNwYmXWRimxnU7X0X7IkcRhyKB4G5DeAmg== -"@swc/core@^1.3.32": - version "1.3.34" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.34.tgz#20a3ea7f3a9bea33ae06358e3c1cc4b77080c970" - integrity sha512-kaOCGRpciMEs2FpCUFaPJSNHgggFteOGZToM88uL5k/CEy0nU/6wzl8kUO5J+rI/8/8vN7qyhM1Ajhyj3WCSsw== +"@swc/core@^1.3.35": + version "1.3.35" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.35.tgz#a96fa52651e56dc00af7b0b83750be33e151bfa8" + integrity sha512-KmiBin0XSVzJhzX19zTiCqmLslZ40Cl7zqskJcTDeIrRhfgKdiAsxzYUanJgMJIRjYtl9Kcg1V/Ip2o2wL8v3w== optionalDependencies: - "@swc/core-darwin-arm64" "1.3.34" - "@swc/core-darwin-x64" "1.3.34" - "@swc/core-linux-arm-gnueabihf" "1.3.34" - "@swc/core-linux-arm64-gnu" "1.3.34" - "@swc/core-linux-arm64-musl" "1.3.34" - "@swc/core-linux-x64-gnu" "1.3.34" - "@swc/core-linux-x64-musl" "1.3.34" - "@swc/core-win32-arm64-msvc" "1.3.34" - "@swc/core-win32-ia32-msvc" "1.3.34" - "@swc/core-win32-x64-msvc" "1.3.34" + "@swc/core-darwin-arm64" "1.3.35" + "@swc/core-darwin-x64" "1.3.35" + "@swc/core-linux-arm-gnueabihf" "1.3.35" + "@swc/core-linux-arm64-gnu" "1.3.35" + "@swc/core-linux-arm64-musl" "1.3.35" + "@swc/core-linux-x64-gnu" "1.3.35" + "@swc/core-linux-x64-musl" "1.3.35" + "@swc/core-win32-arm64-msvc" "1.3.35" + "@swc/core-win32-ia32-msvc" "1.3.35" + "@swc/core-win32-x64-msvc" "1.3.35" "@szmarczak/http-timer@^4.0.5": version "4.0.6" diff --git a/packages/core/package.json b/packages/core/package.json index 500a2663d8..e88c3642fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -125,7 +125,7 @@ }, "dependencies": { "@astronautlabs/jsonpath": "^1.1.0", - "@hapi/call": "^9.0.0", + "@hapi/call": "^9.0.1", "@hapi/subtext": "^7.0.4", "@k8slens/node-fetch": "^6.4.0-beta.13", "@kubernetes/client-node": "^0.18.1", @@ -158,12 +158,12 @@ "hpagent": "^1.2.0", "http-proxy": "^1.18.1", "immer": "^9.0.19", - "joi": "^17.7.0", + "joi": "^17.7.1", "js-yaml": "^4.1.0", "lodash": "^4.17.15", "marked": "^4.2.12", "md5-file": "^5.0.0", - "mobx": "^6.7.0", + "mobx": "^6.8.0", "mobx-observable-history": "^2.0.3", "mobx-react": "^7.6.0", "mobx-utils": "^6.0.4", @@ -202,7 +202,7 @@ "@material-ui/lab": "^4.0.0-alpha.60", "@sentry/types": "^6.19.7", "@swc/cli": "^0.1.61", - "@swc/core": "^1.3.32", + "@swc/core": "^1.3.35", "@swc/jest": "^0.2.24", "@testing-library/dom": "^7.31.2", "@testing-library/jest-dom": "^5.16.5", @@ -252,8 +252,8 @@ "@types/webpack-dev-server": "^4.7.2", "@types/webpack-env": "^1.18.0", "@types/webpack-node-externals": "^2.5.3", - "@typescript-eslint/eslint-plugin": "^5.51.0", - "@typescript-eslint/parser": "^5.51.0", + "@typescript-eslint/eslint-plugin": "^5.52.0", + "@typescript-eslint/parser": "^5.52.0", "adr": "^1.4.3", "ansi_up": "^5.1.0", "chalk": "^4.1.2", @@ -265,11 +265,11 @@ "cross-env": "^7.0.3", "css-loader": "^6.7.3", "deepdash": "^5.3.9", - "dompurify": "^2.4.3", + "dompurify": "^2.4.4", "electron": "^19.1.9", "electron-builder": "^23.6.0", "electron-notarize": "^0.3.0", - "esbuild": "^0.17.7", + "esbuild": "^0.17.8", "esbuild-loader": "^2.21.0", "eslint": "^8.33.0", "eslint-import-resolver-typescript": "^3.5.3", @@ -310,7 +310,7 @@ "react-table": "^7.8.0", "react-window": "^1.8.8", "rimraf": "^4.1.2", - "sass": "^1.58.0", + "sass": "^1.58.1", "sass-loader": "^12.6.0", "style-loader": "^3.3.1", "tailwindcss": "^3.2.4", diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts new file mode 100644 index 0000000000..240ad0f37e --- /dev/null +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/navigate-to-replication-controllers.injectable.ts @@ -0,0 +1,20 @@ +/** + * 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 replicationControllersRouteInjectable from "./replicationcontrollers-route.injectable"; +import { navigateToRouteInjectionToken } from "../../../../navigate-to-route-injection-token"; + +const navigateToReplicationControllersInjectable = getInjectable({ + id: "navigate-to-replicationcontrollers", + + instantiate: (di) => { + const navigateToRoute = di.inject(navigateToRouteInjectionToken); + const route = di.inject(replicationControllersRouteInjectable); + + return () => navigateToRoute(route); + }, +}); + +export default navigateToReplicationControllersInjectable; diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts new file mode 100644 index 0000000000..77d87abc96 --- /dev/null +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts @@ -0,0 +1,24 @@ +/** + * 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 { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; + +const replicationControllersRouteInjectable = getInjectable({ + id: "replicationcontrollers-route", + + instantiate: (di) => ({ + path: "/replicationcontrollers", + clusterFrame: true, + isEnabled: di.inject(shouldShowResourceInjectionToken, { + apiName: "replicationcontrollers", + group: "", // core + }), + }), + + injectionToken: frontEndRouteInjectionToken, +}); + +export default replicationControllersRouteInjectable; diff --git a/packages/core/src/common/k8s-api/endpoints/index.ts b/packages/core/src/common/k8s-api/endpoints/index.ts index 0314c6b282..8fb6cbdabd 100644 --- a/packages/core/src/common/k8s-api/endpoints/index.ts +++ b/packages/core/src/common/k8s-api/endpoints/index.ts @@ -33,6 +33,7 @@ export * from "./pod-metrics.api"; export * from "./pod-security-policy.api"; export * from "./priority-class.api"; export * from "./replica-set.api"; +export * from "./replication-controller.api"; export * from "./resource-quota.api"; export * from "./role.api"; export * from "./role-binding.api"; diff --git a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts new file mode 100644 index 0000000000..6d65f446b5 --- /dev/null +++ b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 { kubeApiInjectionToken } from "../kube-api/kube-api-injection-token"; +import loggerInjectable from "../../logger.injectable"; +import maybeKubeApiInjectable from "../maybe-kube-api.injectable"; +import { ReplicationControllerApi } from "./replication-controller.api"; + +const replicationControllerApiInjectable = getInjectable({ + id: "replication-controller-api", + instantiate: (di) => { + return new ReplicationControllerApi({ + logger: di.inject(loggerInjectable), + maybeKubeApi: di.inject(maybeKubeApiInjectable), + }); + }, + + injectionToken: kubeApiInjectionToken, +}); + +export default replicationControllerApiInjectable; diff --git a/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts new file mode 100644 index 0000000000..b87b7a81a8 --- /dev/null +++ b/packages/core/src/common/k8s-api/endpoints/replication-controller.api.ts @@ -0,0 +1,149 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { DerivedKubeApiOptions, KubeApiDependencies } from "../kube-api"; +import { KubeApi } from "../kube-api"; +import type { + BaseKubeObjectCondition, KubeObjectMetadata, + KubeObjectStatus, + NamespaceScopedMetadata, +} from "../kube-object"; +import { KubeObject } from "../kube-object"; +import type { PodTemplateSpec } from "./types"; + +export class ReplicationControllerApi extends KubeApi { + constructor(deps: KubeApiDependencies, opts?: DerivedKubeApiOptions) { + super(deps, { + ...opts ?? {}, + objectConstructor: ReplicationController, + }); + } + + protected getScaleApiUrl(params: { namespace: string; name: string }) { + return `${this.formatUrlForNotListing(params)}/scale`; + } + + getScale(params: { namespace: string; name: string }): Promise { + return this.request.get(this.getScaleApiUrl(params)); + } + + scale(params: { namespace: string; name: string }, replicas: number): Promise { + return this.request.patch(this.getScaleApiUrl(params), { + data: { + metadata: params, + spec: { + replicas, + }, + }, + }, { + headers: { + "content-type": "application/strategic-merge-patch+json", + }, + }); + } +} + +export interface Scale { + apiVersion: "autoscaling/v1"; + kind: "Scale"; + metadata: KubeObjectMetadata; + spec: { + replicas: number; + }; + status: { + replicas: number; + selector: string; + }; +} + +export interface ReplicationControllerSpec { + /** + * Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. + * Defaults to 0 (pod will be considered available as soon as it is ready) + */ + minReadySeconds?: number; + /** + * Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. + * Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas?: number; + /** + * Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. + * Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. + * More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors + */ + selector?: Record; + /** + * Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template + */ + template: PodTemplateSpec; +} + +export interface ReplicationControllerStatus extends KubeObjectStatus { + /** + * The number of available replicas (ready for at least minReadySeconds) for this replication controller. + */ + availableReplicas: number; + /** + * The number of pods that have labels matching the labels of the pod template of the replication controller. + */ + fullyLabeledReplicas: number; + /** + * ObservedGeneration reflects the generation of the most recently observed replication controller. + */ + observedGeneration: number; + /** + * The number of ready replicas for this replication controller. + */ + readyReplicas: number; + /** + * Replicas is the most recently observed number of replicas. + * More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller + */ + replicas: number; +} + +export class ReplicationController extends KubeObject< + NamespaceScopedMetadata, + ReplicationControllerStatus, + ReplicationControllerSpec +> { + static kind = "ReplicationController"; + static namespaced = true; + static apiBase = "/api/v1/replicationcontrollers"; + + getMinReadySeconds(): number { + return this.spec?.minReadySeconds ?? 0; + } + + getGeneration() { + return this.status?.observedGeneration; + } + + getSelectorLabels(): string[] { + return KubeObject.stringifyLabels(this.spec.selector); + } + + getReplicas(): number | undefined { + return this.status?.replicas; + } + + getDesiredReplicas(): number { + return this.spec?.replicas ?? 0; + } + + getAvailableReplicas(): number | undefined { + return this.status?.availableReplicas; + } + + getLabeledReplicas(): number | undefined { + return this.status?.fullyLabeledReplicas; + } + + getConditions(): BaseKubeObjectCondition[] { + return this.status?.conditions ?? []; + } +} diff --git a/packages/core/src/common/rbac.ts b/packages/core/src/common/rbac.ts index e2ccad3806..03b4fd1de9 100644 --- a/packages/core/src/common/rbac.ts +++ b/packages/core/src/common/rbac.ts @@ -6,7 +6,7 @@ export type KubeResource = "namespaces" | "nodes" | "events" | "resourcequotas" | "services" | "limitranges" | "leases" | "secrets" | "configmaps" | "ingresses" | "ingressclasses" | "networkpolicies" | "persistentvolumeclaims" | "persistentvolumes" | "storageclasses" | - "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "jobs" | "cronjobs" | + "pods" | "daemonsets" | "deployments" | "statefulsets" | "replicasets" | "replicationcontrollers" | "jobs" | "cronjobs" | "endpoints" | "customresourcedefinitions" | "horizontalpodautoscalers" | "verticalpodautoscalers" | "podsecuritypolicies" | "poddisruptionbudgets" | "priorityclasses" | "runtimeclasses" | "roles" | "clusterroles" | "rolebindings" | "clusterrolebindings" | "serviceaccounts"; @@ -171,6 +171,11 @@ export const apiResourceRecord: Record = { group: "apps", namespaced: true, }, + replicationcontrollers: { + kind: "ReplicationController", + group: "", // core + namespaced: true, + }, roles: { kind: "Role", group: "rbac.authorization.k8s.io", diff --git a/packages/core/src/extensions/__tests__/configurable-directories.test.ts b/packages/core/src/extensions/__tests__/configurable-directories.test.ts new file mode 100644 index 0000000000..f8d6f1d678 --- /dev/null +++ b/packages/core/src/extensions/__tests__/configurable-directories.test.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { runInAction } from "mobx"; +import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; +import type { FakeExtensionOptions } from "../../renderer/components/test-utils/get-extension-fake"; +import getHashInjectable from "../../extensions/extension-loader/file-system-provisioner-store/get-hash.injectable"; +import fsInjectable from "../../common/fs/fs.injectable"; + +describe("configurable directories for extension files", () => { + let builder: ApplicationBuilder; + + beforeEach(async () => { + builder = getApplicationBuilder(); + + builder.beforeApplicationStart( + (mainDi) => { + runInAction(() => { + mainDi.override(getHashInjectable, () => x => x); + }); + }, + ); + + await builder.startHidden(); + + const window = builder.applicationWindow.create("some-window-id"); + + await window.start(); + + }); + + describe("when extension with a specific store name is enabled", () => { + let testExtensionOptions: FakeExtensionOptions; + + beforeEach(() => { + + testExtensionOptions = { + id: "some-extension", + name: "some-extension-name", + + mainOptions: { + manifest: { + name: "irrelevant", + storeName: "some-specific-store-name", + version: "0", + engines: { + lens: "0", + }, + }, + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("creates extension directory for specific store name", async () => { + const fs = builder.mainDi.inject(fsInjectable); + + await builder.extensions.get("some-extension").main.getExtensionFileFolder(); + + const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data"); + + expect(nonHashedExtensionDirectories).toContain("some-specific-store-name"); + }); + }); + + describe("when extension with no specific store name is enabled", () => { + let testExtensionOptions: FakeExtensionOptions; + + beforeEach(() => { + + testExtensionOptions = { + id: "some-extension", + name: "some-extension-name", + + mainOptions: { + manifest: { + name: "some-package-name", + storeName: undefined, + version: "0", + engines: { + lens: "0", + }, + }, + }, + }; + + builder.extensions.enable(testExtensionOptions); + }); + + it("creates extension directory for package name", async () => { + const fs = builder.mainDi.inject(fsInjectable); + + await builder.extensions.get("some-extension").main.getExtensionFileFolder(); + + const nonHashedExtensionDirectories = await fs.readdir("/some-directory-for-app-data/some-product-name/extension_data"); + + expect(nonHashedExtensionDirectories).toContain("some-package-name"); + }); + }); +}); diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.ts index c9646a1c6c..bd9baff2c8 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.ts @@ -216,9 +216,6 @@ export class ExtensionDiscovery { const extension = await this.loadExtensionFromFolder(absPath); if (extension) { - // Remove a broken symlink left by a previous installation if it exists. - await this.dependencies.removePath(extension.manifestPath); - // Install dependencies for the new extension await this.dependencies.installExtension(extension.absolutePath); diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts new file mode 100644 index 0000000000..6b696f663d --- /dev/null +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable.ts @@ -0,0 +1,43 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { ObservableMap } from "mobx"; +import { getInjectable } from "@ogre-tools/injectable"; + +import { getOrInsertWithAsync } from "../../../common/utils"; +import randomBytesInjectable from "../../../common/utils/random-bytes.injectable"; +import joinPathsInjectable from "../../../common/path/join-paths.injectable"; +import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable"; +import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable"; +import getHashInjectable from "./get-hash.injectable"; + +export type EnsureHashedDirectoryForExtension = (extensionName: string, registeredExtensions: ObservableMap) => Promise; + +const ensureHashedDirectoryForExtensionInjectable = getInjectable({ + id: "ensure-hashed-directory-for-extension", + + instantiate: (di): EnsureHashedDirectoryForExtension => { + const randomBytes = di.inject(randomBytesInjectable); + const joinPaths = di.inject(joinPathsInjectable); + const directoryForExtensionData = di.inject(directoryForExtensionDataInjectable); + const ensureDirectory = di.inject(ensureDirInjectable); + const getHash = di.inject(getHashInjectable); + + return async (extensionName, registeredExtensions) => { + const dirPath = await getOrInsertWithAsync(registeredExtensions, extensionName, async () => { + const salt = (await randomBytes(32)).toString("hex"); + const hashedName = getHash(`${extensionName}/${salt}`); + + return joinPaths(directoryForExtensionData, hashedName); + }); + + await ensureDirectory(dirPath); + + return dirPath; + }; + }, +}); + +export default ensureHashedDirectoryForExtensionInjectable; diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts index b511437da9..7a9d3c4aa1 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts @@ -4,10 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { FileSystemProvisionerStore } from "./file-system-provisioner-store"; -import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable"; -import ensureDirectoryInjectable from "../../../common/fs/ensure-dir.injectable"; -import joinPathsInjectable from "../../../common/path/join-paths.injectable"; -import randomBytesInjectable from "../../../common/utils/random-bytes.injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import loggerInjectable from "../../../common/logger.injectable"; @@ -17,15 +13,12 @@ import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../ import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file"; import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token"; +import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable"; const fileSystemProvisionerStoreInjectable = getInjectable({ id: "file-system-provisioner-store", instantiate: (di) => new FileSystemProvisionerStore({ - directoryForExtensionData: di.inject(directoryForExtensionDataInjectable), - ensureDirectory: di.inject(ensureDirectoryInjectable), - joinPaths: di.inject(joinPathsInjectable), - randomBytes: di.inject(randomBytesInjectable), directoryForUserData: di.inject(directoryForUserDataInjectable), getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), logger: di.inject(loggerInjectable), @@ -36,6 +29,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({ persistStateToConfig: di.inject(persistStateToConfigInjectionToken), enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), }), }); diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts index 5655a0cf06..7c4fb87361 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts @@ -3,25 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { SHA256 } from "crypto-js"; import { action, makeObservable, observable } from "mobx"; import type { BaseStoreDependencies } from "../../../common/base-store/base-store"; import { BaseStore } from "../../../common/base-store/base-store"; import type { LensExtensionId } from "../../lens-extension"; -import { getOrInsertWithAsync, toJS } from "../../../common/utils"; -import type { EnsureDirectory } from "../../../common/fs/ensure-dir.injectable"; -import type { JoinPaths } from "../../../common/path/join-paths.injectable"; -import type { RandomBytes } from "../../../common/utils/random-bytes.injectable"; +import { toJS } from "../../../common/utils"; +import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable"; interface FSProvisionModel { extensions: Record; // extension names to paths } interface Dependencies extends BaseStoreDependencies { - readonly directoryForExtensionData: string; - ensureDirectory: EnsureDirectory; - joinPaths: JoinPaths; - randomBytes: RandomBytes; + ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension; } export class FileSystemProvisionerStore extends BaseStore { @@ -43,16 +37,7 @@ export class FileSystemProvisionerStore extends BaseStore { * @returns path to the folder that the extension can safely write files to. */ async requestDirectory(extensionName: string): Promise { - const dirPath = await getOrInsertWithAsync(this.registeredExtensions, extensionName, async () => { - const salt = (await this.dependencies.randomBytes(32)).toString("hex"); - const hashedName = SHA256(`${extensionName}/${salt}`).toString(); - - return this.dependencies.joinPaths(this.dependencies.directoryForExtensionData, hashedName); - }); - - await this.dependencies.ensureDirectory(dirPath); - - return dirPath; + return this.dependencies.ensureHashedDirectoryForExtension(extensionName, this.registeredExtensions); } @action diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts new file mode 100644 index 0000000000..5d7faa5884 --- /dev/null +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/get-hash.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import { SHA256 } from "crypto-js"; + +const getHashInjectable = getInjectable({ + id: "get-hash", + + instantiate: () => (text: string) => SHA256(text).toString(), +}); + +export default getHashInjectable; diff --git a/packages/core/src/extensions/extension-store.ts b/packages/core/src/extensions/extension-store.ts index 271175f0a4..e72f394005 100644 --- a/packages/core/src/extensions/extension-store.ts +++ b/packages/core/src/extensions/extension-store.ts @@ -92,6 +92,6 @@ export abstract class ExtensionStore extends BaseStore { protected cwd() { assert(this.extension, "must call this.load() first"); - return path.join(super.cwd(), "extension-store", this.extension.name); + return path.join(super.cwd(), "extension-store", this.extension.storeName); } } diff --git a/packages/core/src/extensions/ipc/ipc-main.ts b/packages/core/src/extensions/ipc/ipc-main.ts index cd18bcc56c..8bb0ada63d 100644 --- a/packages/core/src/extensions/ipc/ipc-main.ts +++ b/packages/core/src/extensions/ipc/ipc-main.ts @@ -27,12 +27,12 @@ export abstract class IpcMain extends IpcRegistrar { listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; const cleanup = once(() => { - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcMain.removeListener(prefixedChannel, listener); }); - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcMain.addListener(prefixedChannel, listener); this.extension[Disposers].push(cleanup); @@ -47,10 +47,10 @@ export abstract class IpcMain extends IpcRegistrar { handle(channel: string, handler: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any): void { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: adding extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: adding extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcMainHandle(prefixedChannel, handler); this.extension[Disposers].push(() => { - this.extension[lensExtensionDependencies].logger.info(`[IPC-RENDERER]: removing extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + this.extension[lensExtensionDependencies].logger.debug(`[IPC-RENDERER]: removing extension handler`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcMain.removeHandler(prefixedChannel); }); diff --git a/packages/core/src/extensions/ipc/ipc-renderer.ts b/packages/core/src/extensions/ipc/ipc-renderer.ts index 71b67779f5..ff61738113 100644 --- a/packages/core/src/extensions/ipc/ipc-renderer.ts +++ b/packages/core/src/extensions/ipc/ipc-renderer.ts @@ -28,12 +28,12 @@ export abstract class IpcRenderer extends IpcRegistrar { listen(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => any): Disposer { const prefixedChannel = `extensions@${this[IpcPrefix]}:${channel}`; const cleanup = once(() => { - console.info(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + console.debug(`[IPC-RENDERER]: removing extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); return ipcRenderer.removeListener(prefixedChannel, listener); }); - console.info(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); + console.debug(`[IPC-RENDERER]: adding extension listener`, { channel, extension: { name: this.extension.name, version: this.extension.version }}); ipcRenderer.addListener(prefixedChannel, listener); this.extension[Disposers].push(cleanup); diff --git a/packages/core/src/extensions/lens-extension.ts b/packages/core/src/extensions/lens-extension.ts index 44848d5f84..53c9343607 100644 --- a/packages/core/src/extensions/lens-extension.ts +++ b/packages/core/src/extensions/lens-extension.ts @@ -27,6 +27,10 @@ export interface LensExtensionManifest extends PackageJson { npm?: string; node?: string; }; + + // Specify extension name used for persisting data. + // Useful if extension is renamed but the data should not be lost. + storeName?: string; } export const lensExtensionDependencies = Symbol("lens-extension-dependencies"); @@ -62,7 +66,10 @@ export class LensExtension< constructor({ id, manifest, manifestPath, isBundled }: InstalledExtension) { makeObservable(this); + + // id is the name of the manifest this.id = id; + this.manifest = manifest; this.manifestPath = manifestPath; this.isBundled = !!isBundled; @@ -80,6 +87,11 @@ export class LensExtension< return this.manifest.description; } + // Name of extension for persisting data + get storeName() { + return this.manifest.storeName || this.name; + } + /** * @ignore */ @@ -93,7 +105,8 @@ export class LensExtension< * folder name. */ async getExtensionFileFolder(): Promise { - return this[lensExtensionDependencies].fileSystemProvisionerStore.requestDirectory(this.id); + // storeName is read from the manifest and has a fallback to the manifest name, which equals id + return this[lensExtensionDependencies].fileSystemProvisionerStore.requestDirectory(this.storeName); } @action diff --git a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts new file mode 100644 index 0000000000..449ce5f4a8 --- /dev/null +++ b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/about-bundled-extensions.injectable.ts @@ -0,0 +1,23 @@ +/** + * 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 { bundledExtensionInjectionToken } from "../../../../../../common/library"; +import buildSemanticVersionInjectable from "../../../../../../common/vars/build-semantic-version.injectable"; + +const aboutBundledExtensionsInjectable = getInjectable({ + id: "about-bundled-extensions", + instantiate: (di) => { + const buildSemanticVersion = di.inject(buildSemanticVersionInjectable); + const bundledExtensions = di.injectMany(bundledExtensionInjectionToken); + + if (buildSemanticVersion.get().prerelease[0] === "latest") { + return []; + } + + return bundledExtensions.map(ext => `${ext.manifest.name}: ${ext.manifest.version}`); + }, +}); + +export default aboutBundledExtensionsInjectable; diff --git a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts index a91e2af337..8bc78ae8c3 100644 --- a/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts +++ b/packages/core/src/features/application-menu/main/menu-items/special-menu-for-mac-application/show-about-application/show-about.injectable.ts @@ -10,6 +10,7 @@ import productNameInjectable from "../../../../../../common/vars/product-name.in import buildVersionInjectable from "../../../../../../main/vars/build-version/build-version.injectable"; import extensionApiVersionInjectable from "../../../../../../common/vars/extension-api-version.injectable"; import applicationCopyrightInjectable from "../../../../../../common/vars/application-copyright.injectable"; +import aboutBundledExtensionsInjectable from "./about-bundled-extensions.injectable"; const showAboutInjectable = getInjectable({ id: "show-about", @@ -22,6 +23,7 @@ const showAboutInjectable = getInjectable({ const appName = di.inject(appNameInjectable); const productName = di.inject(productNameInjectable); const applicationCopyright = di.inject(applicationCopyrightInjectable); + const aboutBundledExtensions = di.inject(aboutBundledExtensionsInjectable); return () => { const appInfo = [ @@ -30,6 +32,7 @@ const showAboutInjectable = getInjectable({ `Electron: ${process.versions.electron}`, `Chrome: ${process.versions.chrome}`, `Node: ${process.versions.node}`, + ...aboutBundledExtensions, applicationCopyright, ]; diff --git a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap index 0936bd6264..3ad0ba73e9 100644 --- a/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap +++ b/packages/core/src/features/cluster/__snapshots__/sidebar-and-tab-navigation-for-extensions.test.tsx.snap @@ -4507,3 +4507,497 @@ exports[`cluster - sidebar and tab navigation for extensions given extension wit `; + +exports[`cluster - sidebar and tab navigation for extensions given extension with cluster pages and cluster page menus with explicit 'orderNumber' given no state for expanded sidebar items exists, and navigated to child sidebar item, when rendered renders 1`] = ` +
+
+
+
+
+ +
+ + + close + + +
+ Close +
+
+
+
+
+
+
+
+