├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── main.ts ├── package-lock.json ├── package.json ├── renderer.tsx ├── src ├── add-debug-image-dialog.tsx ├── debug-pod-menu.tsx ├── debug-pod-preferences-store.ts ├── debug-pod-preferences.tsx └── debug-pod-utils.ts ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | 23 | const packageJson = require("./package.json"); 24 | 25 | module.exports = { 26 | ignorePatterns: [ 27 | "**/node_modules/**/*", 28 | "**/dist/**/*", 29 | "**/static/**/*", 30 | ], 31 | settings: { 32 | react: { 33 | version: packageJson.devDependencies.react || "detect", 34 | } 35 | }, 36 | overrides: [ 37 | { 38 | files: [ 39 | "**/*.js" 40 | ], 41 | extends: [ 42 | "eslint:recommended", 43 | ], 44 | env: { 45 | node: true 46 | }, 47 | parserOptions: { 48 | ecmaVersion: 2018, 49 | sourceType: "module", 50 | }, 51 | plugins: [ 52 | "header", 53 | "unused-imports", 54 | "react-hooks" 55 | ], 56 | rules: { 57 | "header/header": [2, "./LICENSE"], 58 | "indent": ["error", 2, { 59 | "SwitchCase": 1, 60 | }], 61 | "no-unused-vars": "off", 62 | "unused-imports/no-unused-imports": "error", 63 | "unused-imports/no-unused-vars": [ 64 | "warn", { 65 | "vars": "all", 66 | "args": "after-used", 67 | "ignoreRestSiblings": true, 68 | } 69 | ], 70 | "quotes": ["error", "double", { 71 | "avoidEscape": true, 72 | "allowTemplateLiterals": true, 73 | }], 74 | "linebreak-style": ["error", "unix"], 75 | "eol-last": ["error", "always"], 76 | "semi": ["error", "always"], 77 | "object-shorthand": "error", 78 | "prefer-template": "error", 79 | "template-curly-spacing": "error", 80 | "padding-line-between-statements": [ 81 | "error", 82 | { "blankLine": "always", "prev": "*", "next": "return" }, 83 | { "blankLine": "always", "prev": "*", "next": "block-like" }, 84 | { "blankLine": "always", "prev": "*", "next": "function" }, 85 | { "blankLine": "always", "prev": "*", "next": "class" }, 86 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, 87 | { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]}, 88 | ] 89 | } 90 | }, 91 | { 92 | files: [ 93 | "**/*.ts", 94 | ], 95 | parser: "@typescript-eslint/parser", 96 | extends: [ 97 | "plugin:@typescript-eslint/recommended", 98 | ], 99 | plugins: [ 100 | "header", 101 | "unused-imports" 102 | ], 103 | parserOptions: { 104 | ecmaVersion: 2018, 105 | sourceType: "module", 106 | }, 107 | rules: { 108 | "header/header": [2, "./LICENSE"], 109 | "no-invalid-this": "off", 110 | "@typescript-eslint/no-invalid-this": ["error"], 111 | "@typescript-eslint/explicit-function-return-type": "off", 112 | "@typescript-eslint/no-explicit-any": "off", 113 | "@typescript-eslint/explicit-module-boundary-types": "off", 114 | "@typescript-eslint/ban-types": "off", 115 | "@typescript-eslint/ban-ts-comment": "off", 116 | "@typescript-eslint/no-empty-interface": "off", 117 | "@typescript-eslint/no-unused-vars": "off", 118 | "unused-imports/no-unused-imports-ts": "error", 119 | "unused-imports/no-unused-vars-ts": [ 120 | "warn", { 121 | "vars": "all", 122 | "args": "after-used", 123 | "ignoreRestSiblings": true, 124 | } 125 | ], 126 | "indent": ["error", 2, { 127 | "SwitchCase": 1, 128 | }], 129 | "quotes": ["error", "double", { 130 | "avoidEscape": true, 131 | "allowTemplateLiterals": true, 132 | }], 133 | "react/prop-types": "off", 134 | "semi": "off", 135 | "@typescript-eslint/semi": ["error"], 136 | "linebreak-style": ["error", "unix"], 137 | "eol-last": ["error", "always"], 138 | "object-shorthand": "error", 139 | "prefer-template": "error", 140 | "template-curly-spacing": "error", 141 | "padding-line-between-statements": [ 142 | "error", 143 | { "blankLine": "always", "prev": "*", "next": "return" }, 144 | { "blankLine": "always", "prev": "*", "next": "block-like" }, 145 | { "blankLine": "always", "prev": "*", "next": "function" }, 146 | { "blankLine": "always", "prev": "*", "next": "class" }, 147 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, 148 | { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]}, 149 | ] 150 | }, 151 | }, 152 | { 153 | files: [ 154 | "**/*.tsx", 155 | ], 156 | parser: "@typescript-eslint/parser", 157 | plugins: [ 158 | "header", 159 | "unused-imports" 160 | ], 161 | extends: [ 162 | "plugin:@typescript-eslint/recommended", 163 | "plugin:react/recommended", 164 | ], 165 | parserOptions: { 166 | ecmaVersion: 2018, 167 | sourceType: "module", 168 | jsx: true, 169 | }, 170 | rules: { 171 | "header/header": [2, "./LICENSE"], 172 | "no-invalid-this": "off", 173 | "@typescript-eslint/no-invalid-this": ["error"], 174 | "@typescript-eslint/explicit-function-return-type": "off", 175 | "@typescript-eslint/no-explicit-any": "off", 176 | "@typescript-eslint/interface-name-prefix": "off", 177 | "@typescript-eslint/no-use-before-define": "off", 178 | "@typescript-eslint/no-empty-interface": "off", 179 | "@typescript-eslint/no-var-requires": "off", 180 | "@typescript-eslint/ban-ts-ignore": "off", 181 | "@typescript-eslint/explicit-module-boundary-types": "off", 182 | "@typescript-eslint/ban-types": "off", 183 | "@typescript-eslint/no-empty-function": "off", 184 | "react/display-name": "off", 185 | "@typescript-eslint/no-unused-vars": "off", 186 | "unused-imports/no-unused-imports-ts": "error", 187 | "unused-imports/no-unused-vars-ts": [ 188 | "warn", { 189 | "vars": "all", 190 | "args": "after-used", 191 | "ignoreRestSiblings": true, 192 | } 193 | ], 194 | "indent": ["error", 2, { 195 | "SwitchCase": 1, 196 | }], 197 | "quotes": ["error", "double", { 198 | "avoidEscape": true, 199 | "allowTemplateLiterals": true, 200 | }], 201 | "react/prop-types": "off", 202 | "semi": "off", 203 | "@typescript-eslint/semi": ["error"], 204 | "linebreak-style": ["error", "unix"], 205 | "eol-last": ["error", "always"], 206 | "object-shorthand": "error", 207 | "prefer-template": "error", 208 | "template-curly-spacing": "error", 209 | "padding-line-between-statements": [ 210 | "error", 211 | { "blankLine": "always", "prev": "*", "next": "return" }, 212 | { "blankLine": "always", "prev": "*", "next": "block-like" }, 213 | { "blankLine": "always", "prev": "*", "next": "function" }, 214 | { "blankLine": "always", "prev": "*", "next": "class" }, 215 | { "blankLine": "always", "prev": ["const", "let", "var"], "next": "*" }, 216 | { "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"]}, 217 | ], 218 | "react-hooks/rules-of-hooks": "error", 219 | "react-hooks/exhaustive-deps": "off" 220 | }, 221 | } 222 | ] 223 | }; 224 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /dist 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | .vscode 24 | 25 | # Output of `npm/yarn pack` 26 | *.tgz 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # debug-pods-lens-extension 2 | Lens extension, which helps to debug pods 3 | 4 | # what is the extension for? 5 | This extension is useful for interactive troubleshooting when ```kubectl exec``` is insufficient because a container has crashed or a container image doesn't include debugging utilities, such as with distroless images. 6 | 7 | # how is the extension help to debug? 8 | 9 | ### new menu items 10 | This extension adds new menu items to Pods in Workload -> Pods table 11 | The menu includes the next subitems: 12 | 13 | | SubItem | Description | 14 | | ------ | ------ | 15 | | Run as debug pod | Creates and runs new Pod based on with _debug name and placed on the same node/namespace. The Pod is deleted automatically after session closing 16 | | Run as ephemeral image | Attaches and runs ephemeral image to the selected Pod. If the Pod contains 2+ containers you can select which will be linked with an ephemeral container. Warning! Be sure that the current cluster supports this feature 17 | 18 | ### configuration 19 | The extension adds 3 parameters in File -> Preferences 20 | Debug Image: default image, which will be used for ephemeral containers and debug pods, possible values: 21 | | Name | Description | Link | 22 | | ------ | ------ | ------| 23 | | busybox | Default value | https://hub.docker.com/_/busybox | 24 | | markeijsermans/debug | | https://hub.docker.com/r/markeijsermans/debug | 25 | | praqma/network-multitool | | https://hub.docker.com/r/praqma/network-multitool | 26 | 27 | Show all debug images in context menu: shows all debug images as context menu for operation 28 | 29 | Use ephemeral containers: Just hides/shows subitems related to ephemeral containers for each cluster 30 | 31 | # Installation 32 | 33 | ```bash 34 | $ git clone https://github.com/pashevskii/debug-pods-lens-extension.git /your/src/path 35 | $ mkdir -p ~/.k8slens/extensions 36 | $ ln -s /your/src/path ~/.k8slens/extensions/debug-pods-lens-extension 37 | $ cd /your/src/path 38 | $ npm install 39 | $ npm run build 40 | ``` 41 | or 42 | Press CTRL+SHIFT+E on Lens, paste the url and click on Install button: 43 | lens 4: 44 | https://github.com/pashevskii/debug-pods-lens-extension/releases/download/0.1.2/lens-debug-tools-0.1.2.tgz 45 | lens 5: 46 | https://github.com/pashevskii/debug-pods-lens-extension/releases/download/0.1.3/lens-debug-tools-0.1.3.tgz 47 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Main } from "@k8slens/extensions"; 23 | import { debugPodPreferencesStore, DebugPodPreferencesStore } from "./src/debug-pod-preferences-store"; 24 | 25 | export default class DebugPodMainExtension extends Main.LensExtension { 26 | 27 | async onActivate() { 28 | console.log("debug pod main extension activated"); 29 | await debugPodPreferencesStore.loadExtension(this); 30 | } 31 | 32 | async onDeactivate() { 33 | DebugPodPreferencesStore.resetInstance(); 34 | console.log("debug pod main extension deactivated"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lens-debug-tools", 3 | "version": "0.1.3", 4 | "description": "Lens debug tools", 5 | "main": "dist/main.js", 6 | "renderer": "dist/renderer.js", 7 | "engines": { 8 | "lens": "^5.1.2" 9 | }, 10 | "scripts": { 11 | "build": "webpack --config webpack.config.js", 12 | "dev": "npm run build --watch", 13 | "test": "jest --passWithNoTests --env=jsdom src $@", 14 | "lint": "yarn run eslint --ext js,ts,tsx --max-warnings=0 .", 15 | "lint:fix": "yarn run lint --fix" 16 | }, 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "@k8slens/extensions": "^5.1.2", 20 | "@types/react": "^17.0.0", 21 | "@types/react-select": "^3.0.13", 22 | "@typescript-eslint/eslint-plugin": "^4.14.2", 23 | "@typescript-eslint/parser": "^4.0.0", 24 | "conf": "^7.0.1", 25 | "jest": "^26.6.3", 26 | "eslint": "^7.7.0", 27 | "eslint-plugin-header": "^3.1.1", 28 | "eslint-plugin-react": "^7.24.0", 29 | "eslint-plugin-react-hooks": "^4.2.0", 30 | "eslint-plugin-unused-imports": "^1.0.1", 31 | "mobx": "^6.0.4", 32 | "mobx-react": "^6.2.2", 33 | "react": "^16.13.1", 34 | "react-dom": "^16.13.1", 35 | "ts-loader": "^8.0.4", 36 | "typescript": "^4.0.3", 37 | "webpack": "^4.44.2", 38 | "webpack-cli": "^4.2.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /renderer.tsx: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Renderer } from "@k8slens/extensions"; 23 | import { DebugPodToolsMenu, DebugPodToolsMenuProps } from "./src/debug-pod-menu"; 24 | import { DebugPodToolsPreferenceHint, DebugPodToolsPreferenceInput } from "./src/debug-pod-preferences"; 25 | import { debugPodPreferencesStore } from "./src/debug-pod-preferences-store"; 26 | import { DebugPodUtils } from "./src/debug-pod-utils"; 27 | import React from "react"; 28 | 29 | export default class PodDebugMenuRendererExtension extends Renderer.LensExtension { 30 | kubeObjectMenuItems = [ 31 | { 32 | kind: "Pod", 33 | apiVersions: ["v1"], 34 | components: { 35 | MenuItem: (props: DebugPodToolsMenuProps) => 36 | } 37 | }, 38 | ]; 39 | 40 | appPreferences = [ 41 | { 42 | title: "Debug pod tools", 43 | components: { 44 | Hint: () => , 45 | Input: () => 46 | } 47 | } 48 | ]; 49 | 50 | kubeObjectStatusTexts = [ 51 | { 52 | kind: "Pod", 53 | apiVersions: ["v1"], 54 | resolve: (object : Renderer.K8sApi.Pod) => {return DebugPodUtils.showWarnings(object);}, 55 | } 56 | ]; 57 | 58 | onActivate() { 59 | console.log("debug pods extension activated"); 60 | debugPodPreferencesStore.loadExtension(this); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/add-debug-image-dialog.tsx: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Renderer } from "@k8slens/extensions"; 23 | import React from "react"; 24 | import { observable } from "mobx"; 25 | import { observer } from "mobx-react"; 26 | 27 | const {Component} = Renderer; 28 | 29 | interface Props { 30 | onSuccess?(image: string): void; 31 | onError?(error: any): void; 32 | } 33 | 34 | const dialogState = observable.object({ 35 | isOpen: false, 36 | image: "", 37 | }); 38 | 39 | @observer 40 | export class AddDebugImageDialog extends React.Component { 41 | 42 | static open() { 43 | dialogState.isOpen = true; 44 | } 45 | 46 | static close() { 47 | dialogState.isOpen = false; 48 | } 49 | 50 | reset = () => { 51 | dialogState.image = ""; 52 | }; 53 | 54 | close = () => { 55 | AddDebugImageDialog.close(); 56 | }; 57 | 58 | addImage = async () => { 59 | 60 | const { onSuccess, onError } = this.props; 61 | 62 | try { 63 | onSuccess(dialogState.image); 64 | this.close(); 65 | } catch (err) { 66 | Component.Notifications.error(err); 67 | onError && onError(err); 68 | } 69 | }; 70 | 71 | render() { 72 | const { ...dialogProps } = this.props; 73 | const header =
Add new debug image
; 74 | 75 | console.log("Render dialog"); 76 | 77 | return( 78 | 85 | 86 | 91 | dialogState.image = v} 96 | /> 97 | 98 | 99 | 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/debug-pod-menu.tsx: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import React from "react"; 23 | import { Renderer, Common } from "@k8slens/extensions"; 24 | import { DebugPodPreferencesStore } from "./debug-pod-preferences-store"; 25 | import { DebugPodUtils } from "./debug-pod-utils"; 26 | 27 | 28 | const { 29 | Component: { 30 | 31 | createTerminalTab, 32 | terminalStore, 33 | MenuItem, 34 | Icon, 35 | SubMenu, 36 | StatusBrick, 37 | }, 38 | 39 | Navigation, 40 | K8sApi:{ 41 | 42 | }, 43 | 44 | } = Renderer; 45 | const { 46 | Util, 47 | 48 | } = Common; 49 | 50 | export interface DebugPodToolsMenuProps extends Renderer.Component.KubeObjectMenuProps { 51 | 52 | } 53 | 54 | export class DebugPodToolsMenu extends React.Component { 55 | static label = "createdBy=lens-debug-extension"; 56 | static removeOnComplete = true; 57 | 58 | async attachAndRunDebugContainer(container: string, debugImage = DebugPodPreferencesStore.getInstance().debugImage) { 59 | Navigation.hideDetails(); 60 | const { object: pod } = this.props; 61 | let command = DebugPodUtils.getDebugCommand(Renderer.Catalog.catalogEntities.activeEntity as Common.Catalog.KubernetesCluster ); 62 | 63 | command = `${command} -i -t -n ${pod.getNs()} ${pod.getName()} --image=${debugImage} --target ${container} --attach`; 64 | 65 | const shell = createTerminalTab({ 66 | title: `Pod: ${pod.getName()} (namespace: ${pod.getNs()})` 67 | }); 68 | 69 | terminalStore.sendCommand(this.formatCommand(command), { 70 | enter: true, 71 | tabId: shell.id 72 | }); 73 | } 74 | 75 | async createDebugPodAndRun(debugImage = DebugPodPreferencesStore.getInstance().debugImage) { 76 | Navigation.hideDetails(); 77 | const { object: pod } = this.props; 78 | let command = `kubectl run ${pod.getName()}-debug -n ${pod.getNs()} -it --image=${debugImage} --restart=Never --attach `; 79 | 80 | if (pod.getNodeName()) { 81 | command = `${command} --overrides='{ "spec": { "nodeName": "${pod.getNodeName()}" } }'`; 82 | } 83 | 84 | if (DebugPodToolsMenu.label) { 85 | command = `${command} --labels=${DebugPodToolsMenu.label}`; 86 | } 87 | 88 | if (DebugPodToolsMenu.removeOnComplete) { 89 | command = `${command} --rm`; 90 | } 91 | 92 | const shell = createTerminalTab({ 93 | title: `Pod: ${pod.getName()}-debug (namespace: ${pod.getNs()})` 94 | }); 95 | 96 | terminalStore.sendCommand(this.formatCommand(command), { 97 | enter: true, 98 | tabId: shell.id 99 | }); 100 | 101 | } 102 | 103 | formatCommand(command: string):string { 104 | if (window.navigator.platform !== "Win32") return `exec ${command}`; 105 | else return command.replace(/"/g, '\\"'); 106 | } 107 | 108 | renderAllImagesAttach(container: string) { 109 | if (DebugPodPreferencesStore.getInstance().showAllImages && DebugPodPreferencesStore.getInstance().debugImageList.length > 1) { 110 | return ( 111 | 112 | 113 | Select debug image... 114 | 115 | {DebugPodPreferencesStore.getInstance().debugImageList.map(v => { 116 | return ( 117 | this.attachAndRunDebugContainer(container,v))}> 118 | {v} 119 | 120 | ); 121 | })} 122 | 123 | ); 124 | } 125 | } 126 | 127 | renderAllImagesDebug() { 128 | if (DebugPodPreferencesStore.getInstance().showAllImages && DebugPodPreferencesStore.getInstance().debugImageList.length > 1) { 129 | return ( 130 | 131 | 132 | Select debug image... 133 | 134 | {DebugPodPreferencesStore.getInstance().debugImageList.map(v => { 135 | return ( 136 | this.createDebugPodAndRun(v))}> 137 | {v} 138 | 139 | ); 140 | })} 141 | 142 | ); 143 | } 144 | } 145 | 146 | render() { 147 | const { object, toolbar } = this.props; 148 | const { object: pod } = this.props; 149 | const containers = object.getRunningContainers(); 150 | 151 | return ( 152 | 153 | 154 | Debug 155 | <> 156 | 157 | 158 | this.createDebugPodAndRun())} className="flex align-center"> 159 | Run as debug pod 160 | {this.renderAllImagesDebug()} 161 | 162 | {containers.length > 0 && Renderer.Catalog.catalogEntities.activeEntity && DebugPodPreferencesStore.getInstance().ephemeralContainersEnabled.indexOf(Renderer.Catalog.catalogEntities.activeEntity.getName()) > -1 && ( 163 | this.attachAndRunDebugContainer(containers[0].name))} className="flex align-center"> 164 | Run as emepheral container 165 | {containers.length == 1 && this.renderAllImagesAttach(containers[0].name)} 166 | {containers.length > 1 && ( 167 | <> 168 | 169 | 170 | {containers.map(container => { 171 | const { name } = container; 172 | 173 | return ( 174 | this.attachAndRunDebugContainer(name))} className="flex align-center"> 175 | 176 | {name} 177 | {this.renderAllImagesAttach(name)} 178 | 179 | ); 180 | })} 181 | 182 | 183 | )} 184 | 185 | )} 186 | 187 | 188 | 189 | 190 | ); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/debug-pod-preferences-store.ts: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Common } from "@k8slens/extensions"; 23 | import { observable, makeObservable, toJS } from "mobx"; 24 | 25 | export type DebugPodPreferencesModel = { 26 | debugImage: string; 27 | ephemeralContainersEnabled: String[]; 28 | debugImageList: string[]; 29 | showAllImages: boolean; 30 | }; 31 | 32 | export class DebugPodPreferencesStore extends Common.Store.ExtensionStore { 33 | @observable debugImage = "busybox"; 34 | @observable ephemeralContainersEnabled: String[] = []; 35 | @observable debugImageList = ["busybox", "markeijsermans/debug", "praqma/network-multitool"]; 36 | @observable showAllImages = false; 37 | 38 | constructor() { 39 | super({ 40 | configName: "debug-pods-preferences-store", 41 | defaults: { 42 | debugImage: "busybox", 43 | ephemeralContainersEnabled: [], 44 | debugImageList: ["busybox", "markeijsermans/debug", "praqma/network-multitool"], 45 | showAllImages: false 46 | } 47 | }); 48 | makeObservable(this); 49 | } 50 | 51 | protected fromStore( data: DebugPodPreferencesModel): void { 52 | this.debugImage = data.debugImage; 53 | this.ephemeralContainersEnabled = data.ephemeralContainersEnabled; 54 | this.debugImageList = data.debugImageList; 55 | this.showAllImages = data.showAllImages; 56 | } 57 | 58 | toJSON(): DebugPodPreferencesModel { 59 | const value: DebugPodPreferencesModel = { 60 | debugImage: this.debugImage, 61 | ephemeralContainersEnabled: this.ephemeralContainersEnabled, 62 | debugImageList: this.debugImageList, 63 | showAllImages: this.showAllImages, 64 | }; 65 | 66 | return toJS(value); 67 | } 68 | } 69 | 70 | export const debugPodPreferencesStore = DebugPodPreferencesStore.createInstance(); 71 | -------------------------------------------------------------------------------- /src/debug-pod-preferences.tsx: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Common, Renderer } from "@k8slens/extensions"; 23 | import React from "react"; 24 | import { observer } from "mobx-react"; 25 | import { DebugPodPreferencesStore } from "./debug-pod-preferences-store"; 26 | import { AddDebugImageDialog} from "./add-debug-image-dialog"; 27 | import {DebugPodUtils} from "./debug-pod-utils"; 28 | 29 | const {Component} = Renderer; 30 | 31 | @observer 32 | export class DebugPodToolsPreferenceInput extends React.Component { 33 | 34 | 35 | 36 | render() { 37 | const debug = DebugPodPreferencesStore.getInstance(); 38 | 39 | return ( 40 | <> 41 |
Debug Image
42 |
43 | {debug.debugImage = value;}} 48 | /> 49 | AddDebugImageDialog.open()} 53 | /> 54 | {debug.debugImageList = debug.debugImageList.filter(item => item != debug.debugImage); debug.debugImage=debug.debugImageList[0];}} 57 | /> 58 |
59 | {debug.showAllImages = v;}} 63 | /> 64 |
Enable ephemeral containers for the clusters
65 | {Array.from(Renderer.Catalog.catalogEntities.entities).filter(( [_,value]) => value instanceof Common.Catalog.KubernetesCluster ).map(([_, value]) => ( 66 | <> -1} 70 | onChange={v => this.toggleEphemeralContainersAccessibility(v, value.getName())} 71 | /> 72 | {!DebugPodUtils.isClusterSupportEphemeralContainers(value as Common.Catalog.KubernetesCluster) && ( Warning, the cluster doesn't support ephemeral containers or is not available)} 73 | 74 | ))} 75 |
Warning! Be sure that Ephemeral Containers are enabled on your cluster before using this function! More information is 76 | here
77 | {debug.debugImageList.push(v); debug.debugImage = v; }}/> 78 | 79 | ); 80 | } 81 | 82 | /*checkAllClusters() { 83 | Store.clusterStore.getByWorkspaceId(Store.workspaceStore.currentWorkspaceId)[0].activate().then(() => {console.log("Active")}).catch(()=>{console.log("ERROR")}) 84 | }*/ 85 | 86 | 87 | toggleEphemeralContainersAccessibility(value:boolean, name: string) { 88 | const debug = DebugPodPreferencesStore.getInstance(); 89 | const emepheralContainersEnabledSet = new Set(debug.ephemeralContainersEnabled); 90 | 91 | if (value) { 92 | emepheralContainersEnabledSet.add(name); 93 | } else { 94 | emepheralContainersEnabledSet.add(name).delete(name); 95 | } 96 | debug.ephemeralContainersEnabled = Array.from(emepheralContainersEnabledSet); 97 | } 98 | } 99 | 100 | 101 | 102 | export class DebugPodToolsPreferenceHint extends React.Component { 103 | render() { 104 | return ( 105 | <> 106 | 107 | ); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/debug-pod-utils.ts: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | import { Common, Renderer} from "@k8slens/extensions"; 23 | 24 | export class DebugPodUtils{ 25 | static showWarnings(pod : Renderer.K8sApi.Pod):Renderer.K8sApi.KubeObjectStatus { 26 | const podSpecAsObject = pod.spec; //getting things which are not declared in K8sApi.Pod 27 | 28 | if (pod.getLabels().indexOf("createdBy=lens-debug-extension") > -1) { 29 | return {level: 2, text: "It is a debug pod, which is created by Debug Pod Extension, do not forget to remove it after debugging"}; 30 | } 31 | if (podSpecAsObject.ephemeralContainers) { 32 | return {level: 2, text: "This pod contains emepheral containers. Do not forget to restart container after debugging"}; 33 | } 34 | 35 | else return null; 36 | } 37 | 38 | //Ephemeral containers are supported by k8s 1.16+ 39 | static isClusterSupportEphemeralContainers(cluster: Common.Catalog.KubernetesCluster) : boolean { 40 | if (!cluster.metadata.kubeVersion) return false; 41 | const blocks = cluster.metadata.kubeVersion.toString().split(".").map(x=>parseInt(x)); 42 | 43 | return blocks[1] > 15; 44 | } 45 | 46 | // K8s 1.20 uses kubectl debug instead of kubectl alpha debug 47 | static getDebugCommand(cluster: Common.Catalog.KubernetesCluster) : string { 48 | if (!cluster.metadata.kubeVersion) return "kubectl alpha debug"; 49 | const blocks = cluster.metadata.kubeVersion.toString().split(".").map(x=>parseInt(x)); 50 | 51 | console.log("Blocks:", blocks[1]); 52 | if (blocks[1] > 19) return "kubectl debug"; 53 | else return "kubectl alpha debug"; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "CommonJS", 5 | "target": "ES2017", 6 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 7 | "moduleResolution": "Node", 8 | "sourceMap": false, 9 | "declaration": false, 10 | "strict": false, 11 | "noImplicitAny": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "experimentalDecorators": true, 16 | "jsx": "react" 17 | }, 18 | "include": [ 19 | "./*.ts", 20 | "./*.tsx" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "*.js" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*MIT License 2 | 3 | Copyright (c) 2020 Pavel Ashevskii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE.*/ 22 | const path = require("path"); 23 | 24 | module.exports = [ 25 | { 26 | entry: "./main.ts", 27 | context: __dirname, 28 | target: "electron-main", 29 | mode: "production", 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.tsx?$/, 34 | use: "ts-loader", 35 | exclude: /node_modules/, 36 | }, 37 | ], 38 | }, 39 | externals: [ 40 | { 41 | "@k8slens/extensions": "var global.LensExtensions", 42 | "react": "var global.React", 43 | "mobx": "var global.Mobx" 44 | } 45 | ], 46 | resolve: { 47 | extensions: [ ".tsx", ".ts", ".js" ], 48 | }, 49 | output: { 50 | libraryTarget: "commonjs2", 51 | globalObject: "this", 52 | filename: "main.js", 53 | path: path.resolve(__dirname, "dist"), 54 | }, 55 | }, 56 | { 57 | entry: "./renderer.tsx", 58 | context: __dirname, 59 | target: "electron-renderer", 60 | mode: "production", 61 | module: { 62 | rules: [ 63 | { 64 | test: /\.tsx?$/, 65 | use: "ts-loader", 66 | exclude: /node_modules/, 67 | }, 68 | ], 69 | }, 70 | externals: [ 71 | { 72 | "@k8slens/extensions": "var global.LensExtensions", 73 | "react": "var global.React", 74 | "mobx": "var global.Mobx" 75 | } 76 | ], 77 | resolve: { 78 | extensions: [ ".tsx", ".ts", ".js" ], 79 | }, 80 | output: { 81 | libraryTarget: "commonjs2", 82 | globalObject: "this", 83 | filename: "renderer.js", 84 | path: path.resolve(__dirname, "dist"), 85 | }, 86 | }, 87 | ]; 88 | --------------------------------------------------------------------------------