From fa7215a4988c58d0a50bb4e48fc4c7709903a29b Mon Sep 17 00:00:00 2001 From: Andreas Gerler Date: Sun, 26 Apr 2026 19:57:04 +0200 Subject: [PATCH] update kubel --- elpa/kubel-20260424.1403/README-elpa | 1 + .../kubel-autoloads.el | 24 +- .../kubel-pkg.el | 6 +- .../kubel.el | 332 +++++++++++++----- 4 files changed, 258 insertions(+), 105 deletions(-) create mode 100644 elpa/kubel-20260424.1403/README-elpa rename elpa/{kubel-20250713.1542 => kubel-20260424.1403}/kubel-autoloads.el (62%) rename elpa/{kubel-20250713.1542 => kubel-20260424.1403}/kubel-pkg.el (73%) rename elpa/{kubel-20250713.1542 => kubel-20260424.1403}/kubel.el (82%) diff --git a/elpa/kubel-20260424.1403/README-elpa b/elpa/kubel-20260424.1403/README-elpa new file mode 100644 index 00000000..a1c1cc10 --- /dev/null +++ b/elpa/kubel-20260424.1403/README-elpa @@ -0,0 +1 @@ +Emacs extension for controlling Kubernetes with limited permissions. diff --git a/elpa/kubel-20250713.1542/kubel-autoloads.el b/elpa/kubel-20260424.1403/kubel-autoloads.el similarity index 62% rename from elpa/kubel-20250713.1542/kubel-autoloads.el rename to elpa/kubel-20260424.1403/kubel-autoloads.el index 36e78041..0bb89d27 100644 --- a/elpa/kubel-20250713.1542/kubel-autoloads.el +++ b/elpa/kubel-20260424.1403/kubel-autoloads.el @@ -11,30 +11,32 @@ ;;; Generated autoloads from kubel.el -(autoload 'kubel-yaml-editing-mode "kubel" "\ -Kubel Yaml editing mode. +(autoload 'kubel-yaml-editing-mode "kubel" +"Kubel Yaml editing mode. Allows simple apply of the changes made. \\{kubel-yaml-editing-mode-map} -(fn)" t) -(autoload 'kubel-vterm-setup "kubel" "\ -Adds a vterm enty to the KUBEL-EXEC-POP.") -(autoload 'kubel-refresh "kubel" "\ -Refresh the current kubel buffer, calling kubectl using the configured +In addition to any hooks its parent mode `yaml-mode' might have run, +this mode runs the hook `kubel-yaml-editing-mode-hook', as the final +or penultimate step during initialization." t) +(autoload 'kubel-vterm-setup "kubel" +"Adds a vterm enty to the KUBEL-EXEC-POP.") +(autoload 'kubel-refresh "kubel" +"Refresh the current kubel buffer, calling kubectl using the configured context, namespace, and resource. DIRECTORY is optional for TRAMP support. (fn &optional DIRECTORY)" t) -(autoload 'kubel-open "kubel" "\ -Create a new kubel buffer using passed parameters CONTEXT NAMESPACE RESOURCE. +(autoload 'kubel-open "kubel" +"Create a new kubel buffer using passed parameters CONTEXT NAMESPACE RESOURCE. DIRECTORY is optional for TRAMP support. (fn CONTEXT NAMESPACE RESOURCE &optional DIRECTORY)") -(autoload 'kubel "kubel" "\ -Invoke the kubel buffer. +(autoload 'kubel "kubel" +"Invoke the kubel buffer. DIRECTORY is optional for TRAMP support. (fn &optional DIRECTORY)" t) diff --git a/elpa/kubel-20250713.1542/kubel-pkg.el b/elpa/kubel-20260424.1403/kubel-pkg.el similarity index 73% rename from elpa/kubel-20250713.1542/kubel-pkg.el rename to elpa/kubel-20260424.1403/kubel-pkg.el index c1c9bf6c..a909d2df 100644 --- a/elpa/kubel-20250713.1542/kubel-pkg.el +++ b/elpa/kubel-20260424.1403/kubel-pkg.el @@ -1,5 +1,5 @@ ;; -*- no-byte-compile: t; lexical-binding: nil -*- -(define-package "kubel" "20250713.1542" +(define-package "kubel" "20260424.1403" "Control Kubernetes with limited permissions." '((transient "0.1.0") (emacs "25.3") @@ -7,6 +7,6 @@ (s "1.2.0") (yaml-mode "0.0.14")) :url "https://github.com/abrochard/kubel" - :commit "9218d91b1085a4cdb240377c335142909df6a9e3" - :revdesc "9218d91b1085" + :commit "2161e9ae06d2944b5ad313c642948d752f69add0" + :revdesc "2161e9ae06d2" :keywords '("kubernetes" "k8s" "tools" "processes")) diff --git a/elpa/kubel-20250713.1542/kubel.el b/elpa/kubel-20260424.1403/kubel.el similarity index 82% rename from elpa/kubel-20250713.1542/kubel.el rename to elpa/kubel-20260424.1403/kubel.el index bc8c4064..5f739110 100644 --- a/elpa/kubel-20250713.1542/kubel.el +++ b/elpa/kubel-20260424.1403/kubel.el @@ -21,8 +21,8 @@ ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ;; USA -;; Package-Version: 20250713.1542 -;; Package-Revision: 9218d91b1085 +;; Package-Version: 20260424.1403 +;; Package-Revision: 2161e9ae06d2 ;; Author: Adrien Brochard ;; Keywords: kubernetes k8s tools processes ;; URL: https://github.com/abrochard/kubel @@ -269,6 +269,12 @@ This is used by `kubel-kill-buffer'." :type 'boolean :group 'kubel) +(defcustom kubel-default-namespace "default" + "Default namespace for kubel to use. Change if you have no resources in +`default' namespace." + :type 'string + :group 'kubel) + (defun kubel--append-to-process-buffer (str) "Append string STR to the process buffer." (with-current-buffer (get-buffer-create kubel--process-buffer) @@ -278,6 +284,12 @@ This is used by `kubel-kill-buffer'." (defvar-local kubel--last-command nil) +(defvar-local kubel--header-error nil + "Current error message to display, or nil if no error.") + +(defvar-local kubel--error-overlay nil + "Overlay used to display error message at top of buffer.") + (defun kubel--log-command (process-name cmd) "Log the kubectl command to the process buffer. @@ -288,25 +300,60 @@ CMD is the kubectl command as a list." (kubel--append-to-process-buffer (format "[%s]\ncommand: %s" process-name str-cmd)))) -(defun kubel--exec-to-string (cmd) - "Replace \"shell-command-to-string\" to log to process buffer. - -CMD is the command string to run." +(defun kubel--set-header-error (error-msg) + "Set ERROR-MSG as an overlay at the top of the buffer." + (kubel--clear-header-error) + (setq kubel--header-error error-msg) + (when error-msg + (let ((ov (make-overlay (point-min) (point-min)))) + (overlay-put ov 'before-string + (propertize (format "⚠ Kubel error: %s [Type `$' for details]\n" error-msg) + 'face 'error)) + (overlay-put ov 'kubel-error t) + (setq kubel--error-overlay ov)))) + +(defun kubel--clear-header-error () + "Clear any error overlay from the buffer." + (when kubel--error-overlay + (delete-overlay kubel--error-overlay) + (setq kubel--error-overlay nil)) + (setq kubel--header-error nil)) + +(defun kubel--handle-stderr (stderr-content) + "Handle stderr output by logging to process buffer and displaying in header-line. +STDERR-CONTENT is the stderr string from a kubectl command." + (when (and stderr-content (not (string-empty-p stderr-content))) + (let ((trimmed (string-trim stderr-content))) + (kubel--append-to-process-buffer (format "stderr: %s" trimmed)) + (kubel--set-header-error trimmed)))) + +(defun kubel--exec-sync (cmd &optional silence-warnings) + "Run CMD synchronously and return output as string. +Logs to process buffer and displays any stderr via `kubel--handle-stderr'. +If SILENCE-WARNINGS is non-nil, stderr output is not displayed." (kubel--log-command "kubectl-command" cmd) - (with-output-to-string - (with-current-buffer standard-output - (shell-command cmd t "*kubel stderr*")))) - -(defvar-local kubel-namespace "default" + (let ((stderr-buffer (generate-new-buffer " *kubel-stderr-temp*")) + (result nil)) + (unwind-protect + (progn + (setq result + (with-output-to-string + (with-current-buffer standard-output + (shell-command cmd t stderr-buffer)))) + (let ((stderr-output (with-current-buffer stderr-buffer (buffer-string)))) + (unless silence-warnings + (kubel--handle-stderr stderr-output)))) + (kill-buffer stderr-buffer)) + result)) + +(defvar-local kubel-namespace kubel-default-namespace "Current namespace.") (defvar-local kubel-resource "pods" "Current resource.") -(defvar-local kubel-context - (replace-regexp-in-string - "\n" "" (kubel--exec-to-string "kubectl config current-context")) - "Current context. Tries to smart default.") +(defvar-local kubel-context nil + "Current context. Initialized lazily on first use.") (defvar-local kubel-resource-filter "" "Substring filter for resource name.") @@ -330,6 +377,10 @@ CMD is the command string to run." (defvar-local kubel--kubernetes-resources-list-cached nil) +(defvar-local kubel--parent-buffer nil + "The buffer that spawned this kubel buffer. +Used for back-navigation when pressing q.") + (defun kubel--kubernetes-resources-list () "Get list of resources from cache or from fetching the api resource." (if (null kubel--kubernetes-resources-list-cached) @@ -346,7 +397,7 @@ CMD is the command string to run." (defun kubel--populate-list () "Return a list with a tabulated list format and \"tabulated-list-entries\"." - (let* ((body (kubel--exec-to-string (concat (kubel--get-command-prefix) " get " kubel-resource (if kubel-list-wide " -o wide" "")))) + (let* ((body (kubel--exec-sync (concat (kubel--get-command-prefix) " get " kubel-resource (if kubel-list-wide " -o wide" "")))) (entrylist (kubel--parse-body body))) (when (string-prefix-p "No resources found" body) (message "No resources found")) ;; TODO exception here @@ -472,15 +523,27 @@ If MAX is the end of the line, dynamically adjust." "Return non-nil if there are items selected." (>= (length kubel--selected-items) 1)) +(defun kubel--print-entry (id cols) + "Custom entry printer that applies `dired-marked' face to selected items. +ID is the entry identifier (resource name). +COLS is the vector of column values. +When selected, prepends `*' to NAME and applies `dired-marked' face to entire row." + (let* ((is-selected (-contains? kubel--selected-items id)) + (first-col (aref cols 0)) + (modified-cols (if is-selected + (vconcat (vector (propertize (concat "*" first-col) 'face 'dired-marked)) + (mapcar (lambda (col) (propertize col 'face 'dired-marked)) + (seq-drop cols 1))) + cols))) + (tabulated-list-print-entry id modified-cols))) + (defun kubel--propertize-status (status) "Return the status in proper font color. STATUS is the pod status string." (let ((status-face (cdr (assoc status kubel-status-faces))) - (match (or (equal kubel-resource-filter "") (string-match-p kubel-resource-filter status))) - (selected (and (kubel--items-selected-p) (-contains? kubel--selected-items status)))) + (match (or (equal kubel-resource-filter "") (string-match-p kubel-resource-filter status)))) (cond (status-face (propertize status 'face status-face)) - (selected (propertize (concat "*" status) 'face 'dired-marked)) ((not match) (propertize status 'face 'shadow)) (t status)))) @@ -497,28 +560,30 @@ NAME is the buffer name." (format "*%s:err*" process-name)) (defun kubel--sentinel (callback) - "Sentinel function used by KUBEL--EXEC. - -CALLBACK is called when process completes successfully. -" + "Sentinel function used by `kubel--exec-async'. +CALLBACK is called when process completes successfully." (lambda (process event) - (let ((process-name (process-name process)) - (exit-status (process-exit-status process))) + (let* ((process-name (process-name process)) + (exit-status (process-exit-status process)) + (error-buffer (kubel--process-error-buffer process-name))) (kubel--append-to-process-buffer (format "[%s]\nexit-code: %s" process-name exit-status)) (if (eq 0 exit-status) - (when callback (funcall callback)) - (let ((err (with-current-buffer (kubel--process-error-buffer process-name) - (buffer-string)))) - (kubel--append-to-process-buffer (format "error: %s" err)) - (error (format "Kubel process %s error: %s" process-name err))))))) - -(defun kubel--exec (process-name args &optional readonly callback) - "Utility function to run commands in the proper context and namespace. + (progn + (kubel--clear-header-error) + (when callback (funcall callback))) + (kubel--handle-stderr (with-current-buffer error-buffer + (buffer-string)))) + ;; Clean up error buffer + (when (get-buffer error-buffer) + (kill-buffer error-buffer))))) + +(defun kubel--exec-async (process-name args &optional readonly callback) + "Run kubectl asynchronously with ARGS, showing output in a buffer. PROCESS-NAME is an identifier for the process. Default to \"kubel-command\". -ARGS is a ist of arguments. +ARGS is a list of arguments. CALLBACK is a function that will be executed when the command completes. -READONLY If true buffer will be in readonly mode(view-mode)." +READONLY if non-nil, buffer will be in `view-mode'." (when (equal process-name "") (setq process-name "kubel-command")) (let ((buffer-name (format "*kubel-resource:%s:%s:%s*" kubel-context kubel-namespace (string-join args "_"))) @@ -535,22 +600,22 @@ READONLY If true buffer will be in readonly mode(view-mode)." :file-handler t :stderr (get-buffer-create error-buffer) :command cmd) - (pop-to-buffer buffer-name) + (pop-to-buffer-same-window buffer-name) (if readonly (with-current-buffer buffer-name (view-mode))))) (defun kubel--get-resource-under-cursor () "Utility function to get the name of the resource under the cursor. -Strip the `*` prefix if the resource is selected" +Strips leading `*' mark indicator if present." (string-remove-suffix " (default)" ;; see https://github.com/abrochard/kubel/issues/106 (replace-regexp-in-string - "^\*" "" (aref (tabulated-list-get-entry) 0)))) + "^\\*" "" (aref (tabulated-list-get-entry) 0)))) (defun kubel--get-context-namespace () "Utility function to return the proper context and namespace arguments." (append - (unless (equal kubel-context "") + (unless (or (equal kubel-context nil) (equal kubel-context "")) (list "--context" kubel-context)) (unless (equal kubel-namespace "") (list "-n" kubel-namespace)))) @@ -571,7 +636,7 @@ POD-NAME is the name of the pod. TYPE is containers or initContainers." (unless type (setq type "containers")) (split-string - (kubel--exec-to-string + (kubel--exec-sync (format "%s get pod %s -o jsonpath='{.spec.%s[*].name}'" (kubel--get-command-prefix) pod-name type)) " ")) (defun kubel--get-pod-labels () @@ -582,7 +647,7 @@ TYPE is containers or initContainers." (regexp-quote ":") "=" (replace-regexp-in-string "map\\[\\(.+?\\)\\]" "\\1" - (kubel--exec-to-string + (kubel--exec-sync (format "%s get pod -o jsonpath='{.items[*].metadata.labels}'" (kubel--get-command-prefix))))))) (splitted (mapcan (lambda (s) (split-string s ",")) raw-labels)) @@ -597,7 +662,7 @@ NAME is the string name of the resource." (let ((cmd (format "%s get %s -o=jsonpath='{.items[*].metadata.name}'" (kubel--get-command-prefix) name))) (completing-read (concat (s-upper-camel-case name) ": ") - (split-string (kubel--exec-to-string cmd) " ")))) + (split-string (kubel--exec-sync cmd) " ")))) (defun kubel--describe-resource (name &optional describe) "Describe a specific resource. @@ -605,15 +670,21 @@ NAME is the string name of the resource." NAME is the string name of the resource to decribe. DESCRIBE is boolean to describe instead of get resource details" (let* ((resource (kubel--select-resource name)) + (ctx kubel-context) + (ns kubel-namespace) + (res kubel-resource) (process-name (format "kubel - %s - %s" name resource)) (callback (lambda () (set-buffer-modified-p nil) (goto-char (point-min))))) (if describe - (kubel--exec process-name (list "describe" name resource) nil callback) - (kubel--exec process-name (list "get" name "-o" kubel-output resource) nil callback)) + (kubel--exec-async process-name (list "describe" name resource) nil callback) + (kubel--exec-async process-name (list "get" name "-o" kubel-output resource) nil callback)) (when (string-equal kubel-output "yaml") - (kubel-yaml-editing-mode)))) + (kubel-yaml-editing-mode) + (setq kubel-context ctx) + (setq kubel-namespace ns) + (setq kubel-resource res)))) (defun kubel--show-rollout-revision (type name) "Show a specific revision of a certain resource. @@ -624,7 +695,7 @@ NAME is the resource name." (revision (car (split-string (kubel--select-rollout typename)))) (process-name (format "kubel - rollout - %s - %s" typename revision)) (callback (goto-char (point-min)))) - (kubel--exec process-name + (kubel--exec-async process-name (list "rollout" "history" typename (format "--revision=%s" revision)) nil callback))) (defun kubel--list-rollout (typename) @@ -632,7 +703,7 @@ NAME is the resource name." TYPENAME is the resource type/name." (let ((cmd (format "%s rollout history %s" (kubel--get-command-prefix) typename))) - (nthcdr 2 (split-string (kubel--exec-to-string cmd) "\n" t)))) + (nthcdr 2 (split-string (kubel--exec-sync cmd) "\n" t)))) (defun kubel--select-rollout (typename) "Select a rollout version. @@ -665,6 +736,27 @@ TYPENAME is the resource type/name." (yes-or-no-p "Resource modified; kill anyway? ")) (kill-buffer (current-buffer)))) +(defun kubel-go-back () + "Kill current buffer and return to parent kubel buffer. +If no parent buffer exists, call `quit-window'." + (interactive) + (let ((parent kubel--parent-buffer)) + (if (and parent (buffer-live-p parent)) + (progn + (kill-buffer (current-buffer)) + (switch-to-buffer parent) + ;; Re-display error overlay if there was an error + (when kubel--header-error + (kubel--set-header-error kubel--header-error))) + (quit-window)))) + +(defun kubel-kill-all-buffers () + "Kill all kubel-related buffers." + (interactive) + (dolist (buf (buffer-list)) + (when (string-prefix-p "*kubel:" (buffer-name buf)) + (kill-buffer buf)))) + (defvar kubel-yaml-editing-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "C-c C-c") #'kubel-apply) @@ -698,7 +790,7 @@ Allows simple apply of the changes made. (unless (file-exists-p (format "%s/tmp/kubel" dir-prefix)) (make-directory (format "%s/tmp/kubel" dir-prefix) t)) (write-region (point-min) (point-max) filename) - (kubel--exec (format "kubectl - apply - %s" filename) (list "apply" "-f" filename-without-tramp-prefix) nil (lambda () (message "Applied %s" filename)))))) + (kubel--exec-async (format "kubectl - apply - %s" filename) (list "apply" "-f" filename-without-tramp-prefix) nil (lambda () (message "Applied %s" filename)))))) (defun kubel-get-resource-details (&optional describe) "Get the details of the resource under the cursor. @@ -711,16 +803,14 @@ Allows simple apply of the changes made. (res kubel-resource) (process-name (format "kubel - %s - %s" kubel-resource resource)) (callback (lambda () + (setq kubel-context ctx) + (setq kubel-namespace ns) + (setq kubel-resource res) (set-buffer-modified-p nil) (goto-char (point-min))))) (if describe - (kubel--exec process-name (list "describe" kubel-resource (kubel--get-resource-under-cursor)) nil callback) - (kubel--exec process-name (list "get" kubel-resource (kubel--get-resource-under-cursor) "-o" kubel-output) nil callback)) - (when (or (string-equal kubel-output "yaml") (transient-args 'kubel-describe-popup)) - (kubel-yaml-editing-mode) - (setq kubel-context ctx) - (setq kubel-namespace ns) - (setq kubel-resource res)))) + (kubel--exec-async process-name (list "describe" kubel-resource (kubel--get-resource-under-cursor)) t callback) + (kubel--exec-async process-name (list "get" kubel-resource (kubel--get-resource-under-cursor) "-o" kubel-output) t callback)))) (defun kubel--default-tail-arg (args) "Ugly function to make sure that there is at least the default tail. @@ -749,7 +839,7 @@ TYPE is containers or initContainers." (car containers) (completing-read "Select container: " containers))) (process-name (format "kubel - logs - %s - %s" pod container))) - (kubel--exec process-name + (kubel--exec-async process-name (append '("logs") (kubel--default-tail-arg args) (list pod container)) t nil)))) (defun kubel-get-pod-logs--initContainer (&optional args) @@ -768,7 +858,7 @@ ARGS is the arguments list from transient." (let* ((labels (kubel--get-pod-labels)) (label (completing-read "Select container: " labels)) (process-name (format "kubel - logs - %s" label))) - (kubel--exec process-name + (kubel--exec-async process-name (append '("logs") (kubel--default-tail-arg args) '("-l") (list label)) t nil))) (defun kubel-copy-resource-name () @@ -816,16 +906,17 @@ ARGS is the arguments list from transient." (progn (unless kubel--can-get-namespace-cached (setq kubel--can-get-namespace-cached - (string-match-p "yes\n" - (kubel--exec-to-string - (format "%s --context %s auth can-i list namespaces" kubel-kubectl kubel-context)))))) + (string-match-p "yes" + (kubel--exec-sync + (format "%s --context %s auth can-i list namespaces" kubel-kubectl kubel-context) + t))))) ; silence warnings kubel--can-get-namespace-cached))) (defun kubel--get-namespace () "Get namespaces for current context, try to recover from cache first." (unless kubel--namespace-list-cached (setq kubel--namespace-list-cached - (split-string (kubel--exec-to-string + (split-string (kubel--exec-sync (format "%s --context %s get namespace -o jsonpath='{.items[*].metadata.name}'" kubel-kubectl kubel-context)) " "))) kubel--namespace-list-cached) @@ -840,19 +931,48 @@ ARGS is the arguments list from transient." (unless (member namespace kubel-namespace-history) (push namespace kubel-namespace-history))) +(defun kubel-list-namespaces () + "List all namespaces in the current context." + (interactive) + (let* ((kubel--buffer (get-buffer (kubel--buffer-name))) + (parent-buffer (current-buffer)) + (last-default-directory (when kubel--buffer + (with-current-buffer kubel--buffer default-directory)))) + (with-current-buffer (clone-buffer) + (setq kubel--parent-buffer parent-buffer) + (setq kubel-resource "namespaces") + (setq kubel-selector "") + (switch-to-buffer (current-buffer)) + (kubel-refresh last-default-directory)))) + (defun kubel-set-namespace (&optional refresh) "Set the namespace. If called with a prefix argument REFRESH, refreshes -the context caches, including the cached resource list." +the context caches, including the cached resource list. +When viewing namespaces, selects the namespace under cursor, +or the marked namespace if exactly one is marked." (interactive "P") (when refresh (kubel--invalidate-context-caches)) - (let* ((namespace (completing-read "Namespace: " (kubel--list-namespace) - nil nil nil nil "default")) + (let* ((from-namespace-list (equal kubel-resource "namespaces")) + (namespace (if from-namespace-list + (cond + ((> (length kubel--selected-items) 1) + (user-error "Cannot set namespace: multiple namespaces are marked")) + ((= (length kubel--selected-items) 1) + (car kubel--selected-items)) + (t + (kubel--get-resource-under-cursor))) + (completing-read "Namespace: " (kubel--list-namespace) + nil nil nil nil "default"))) (kubel--buffer (get-buffer (kubel--buffer-name))) + (parent-buffer (current-buffer)) (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) (with-current-buffer (clone-buffer) + (setq kubel--parent-buffer parent-buffer) (setq kubel-namespace namespace) + (when from-namespace-list + (setq kubel-resource "pods")) (kubel--add-namespace-to-history namespace) (switch-to-buffer (current-buffer)) (kubel-refresh last-default-directory)))) @@ -861,12 +981,14 @@ the context caches, including the cached resource list." "Set the context." (interactive) (let* ((kubel--buffer (get-buffer (kubel--buffer-name))) + (parent-buffer (current-buffer)) (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) (with-current-buffer (clone-buffer) + (setq kubel--parent-buffer parent-buffer) (setq kubel-context (completing-read "Select context: " - (split-string (kubel--exec-to-string (format "%s config view -o jsonpath='{.contexts[*].name}'" kubel-kubectl)) " "))) + (split-string (kubel--exec-sync (format "%s config view -o jsonpath='{.contexts[*].name}'" kubel-kubectl)) " "))) (kubel--invalidate-context-caches) (setq kubel-namespace "default") (switch-to-buffer (current-buffer)) @@ -893,21 +1015,23 @@ the context caches, including the cached resource list." (defun kubel-set-label-selector () "Set the selector." (interactive) - (with-current-buffer (clone-buffer) - (let ((selector (completing-read + (let ((parent-buffer (current-buffer))) + (with-current-buffer (clone-buffer) + (setq kubel--parent-buffer parent-buffer) + (let ((selector (completing-read "Selector: " (kubel--list-selectors)))) (when (equal selector "none") (setq selector "")) (setq kubel-selector selector)) - (kubel--add-selector-to-history kubel-selector) - ;; Update pod list according to the label selector - (switch-to-buffer (current-buffer)) - (kubel-refresh))) + (kubel--add-selector-to-history kubel-selector) + ;; Update pod list according to the label selector + (switch-to-buffer (current-buffer)) + (kubel-refresh)))) (defun kubel--fetch-api-resource-list () "Fetch the API resource list." - (split-string (kubel--exec-to-string + (split-string (kubel--exec-sync (format "%s --context %s api-resources -o name --no-headers=true" kubel-kubectl kubel-context)) "\n" t)) (defun kubel-set-resource (&optional refresh) @@ -919,8 +1043,10 @@ the context caches, including the cached resource list." (let* ((current-buffer-name (kubel--buffer-name)) (resource-list (kubel--kubernetes-resources-list)) (kubel--buffer (get-buffer current-buffer-name)) + (parent-buffer (current-buffer)) (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) (with-current-buffer (clone-buffer) + (setq kubel--parent-buffer parent-buffer) (setq kubel-resource (completing-read "Select resource: " resource-list)) (switch-to-buffer (current-buffer)) @@ -944,7 +1070,7 @@ P can be a single number or a localhost:container port pair." (kubel--get-resource-under-cursor) (kubel--select-resource "Pods"))) (process-name (format "kubel - port-forward - %s:%s" pod port))) - (kubel--exec process-name (list "port-forward" pod port)))) + (kubel--exec-async process-name (list "port-forward" pod port)))) (defun kubel-setup-tramp () "Setup a kubectl TRAMP." @@ -1087,7 +1213,7 @@ the variables `kubel-namespace' and `kubel-context', respectively." (args (list "delete" kubel-resource pod))) (when (transient-args 'kubel-delete-popup) (setq args (append args (list "--force" "--grace-period=0")))) - (kubel--exec process-name args)))) + (kubel--exec-async process-name args)))) (defun kubel-jab-deployment () "Make a trivial patch to force a new deployment. @@ -1100,7 +1226,7 @@ See https://github.com/kubernetes/kubernetes/issues/27081" (list (kubel--get-resource-under-cursor))) (list (kubel--select-resource "Deployments")))) (let ((process-name (format "kubel - bouncing - %s" deployment))) - (kubel--exec process-name (list "patch" "deployment" deployment "-p" + (kubel--exec-async process-name (list "patch" "deployment" deployment "-p" (format "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"%s\"}}}}}" (round (time-to-seconds)))))))) @@ -1112,7 +1238,7 @@ REPLICAS is the number of desired replicas." (if (kubel--is-scalable) (let* ((resource (kubel--get-resource-under-cursor)) (process-name (format "kubel:scale:%s/%s" kubel-resource resource))) - (kubel--exec process-name (list "scale" kubel-resource resource "--replicas" (number-to-string replicas)))) + (kubel--exec-async process-name (list "scale" kubel-resource resource "--replicas" (number-to-string replicas)))) (message "[%s] cannot be scaled.\nOnly these resources can be scaled: [deployment, replica set, replication controller, and stateful set]." kubel-resource))) @@ -1173,27 +1299,34 @@ RESET is to be called if the search is nil after the first attempt." (defun kubel-show-process-buffer () "Show the kubel-process-buffer." (interactive) - (pop-to-buffer kubel--process-buffer) - (special-mode)) + (with-current-buffer (get-buffer-create kubel--process-buffer) + (special-mode)) + (select-window + (display-buffer kubel--process-buffer + '(display-buffer-below-selected + (window-height . 0.4))))) (defun kubel-mark-item () - "Mark or unmark the item under cursor." + "Mark the item under cursor and move to next line." (interactive) - (let ((item (kubel--get-resource-under-cursor))) + (let ((item (kubel--get-resource-under-cursor)) + (col (current-column))) (unless (-contains? kubel--selected-items item) - (progn - (push item kubel--selected-items) - (forward-line 1) - (kubel-refresh))))) + (push item kubel--selected-items) + (forward-line 1) + (move-to-column col) + (tabulated-list-print t)))) (defun kubel-unmark-item () - "Unmark the item under cursor." + "Unmark the item under cursor and move to next line." (interactive) - (let ((item (kubel--get-resource-under-cursor))) + (let ((item (kubel--get-resource-under-cursor)) + (col (current-column))) (when (-contains? kubel--selected-items item) - (progn - (setq kubel--selected-items (delete item kubel--selected-items)) - (kubel-refresh))))) + (setq kubel--selected-items (delete item kubel--selected-items)) + (forward-line 1) + (move-to-column col) + (tabulated-list-print t)))) (defun kubel-mark-all () "Mark all items." @@ -1204,13 +1337,13 @@ RESET is to be called if the search is nil after the first attempt." (while (not (eobp)) (push (kubel--get-resource-under-cursor) kubel--selected-items) (forward-line 1))) - (kubel-refresh)) + (tabulated-list-print t)) (defun kubel-unmark-all () "Unmark all items." (interactive) (setq kubel--selected-items '()) - (kubel-refresh)) + (tabulated-list-print t)) (defun kubel--read-buffer () "Return the list of all buffers of kubel pattern." @@ -1300,6 +1433,7 @@ When called interactively, prompts for a buffer belonging to kubel." ["Settings" ("C" "Set context" kubel-set-context) ("n" "Set namespace" kubel-set-namespace) + ("y" "List namespaces" kubel-list-namespaces) ("R" "Set resource" kubel-set-resource) ("K" "Set kubectl config file" kubel-set-kubectl-config-file) ("F" "Set output format" kubel-set-output-format)] @@ -1325,6 +1459,7 @@ When called interactively, prompts for a buffer belonging to kubel." (define-key map (kbd "K") 'kubel-set-kubectl-config-file) (define-key map (kbd "C") 'kubel-set-context) (define-key map (kbd "n") 'kubel-set-namespace) + (define-key map (kbd "y") 'kubel-list-namespaces) (define-key map (kbd "g") 'kubel-refresh) (define-key map (kbd "h") 'kubel-help-popup) (define-key map (kbd "?") 'kubel-help-popup) @@ -1353,6 +1488,9 @@ When called interactively, prompts for a buffer belonging to kubel." (define-key map (kbd "M") 'kubel-mark-all) (define-key map (kbd "U") 'kubel-unmark-all) + (define-key map (kbd "q") 'kubel-go-back) + (define-key map (kbd "Q") 'kubel-kill-all-buffers) + map) "Keymap for `kubel-mode'.") @@ -1370,6 +1508,7 @@ context, namespace, and resource. DIRECTORY is optional for TRAMP support." (interactive) + (kubel--clear-header-error) (when directory (setq default-directory directory)) (let ((name (kubel--buffer-name))) ;; Remove old buffer if exist but not is current buffer @@ -1410,6 +1549,15 @@ DIRECTORY is optional for TRAMP support." (pop-to-buffer-same-window tmpname) (kubel-refresh directory))))) +(defun kubel--init-context () + "Initialize context and namespace if not already set. +Defers the kubectl call until first use to avoid blocking Emacs startup." + (unless kubel-context + (setq kubel-context + (replace-regexp-in-string + "\n" "" (kubel--exec-sync "kubectl config current-context"))) + (setq kubel-namespace kubel-default-namespace))) + ;;;###autoload (defun kubel (&optional directory) "Invoke the kubel buffer. @@ -1422,6 +1570,7 @@ DIRECTORY is optional for TRAMP support." (switch-to-buffer (current-buffer)) (unless (eq major-mode 'kubel-mode) (kubel-mode)) + (kubel--init-context) (kubel-refresh directory)))) (define-derived-mode kubel-mode tabulated-list-mode "Kubel" @@ -1431,6 +1580,7 @@ DIRECTORY is optional for TRAMP support." (setq truncate-lines t) (setq mode-name "Kubel") (setq major-mode 'kubel-mode) + (setq-local tabulated-list-printer #'kubel--print-entry) (use-local-map kubel-mode-map) (hl-line-mode 1) (run-mode-hooks 'kubel-mode-hook)) -- 2.47.3