From 558c5f3caee38e65502c228e1666ba95ee415deb Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 9 Sep 2023 12:39:39 +0000 Subject: [PATCH] Multi buffers support (take #57 and rebase) --- README.md | 6 +++ kubel.el | 159 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 109 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 7898941..e1d752e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ We now support managing pretty much any resource! - exec into a pod using tramp - quick run shell-command - scale replicas +- multiple kubel buffers, each one with different context, namespace, and resource. ## Installation @@ -76,6 +77,11 @@ To programmatically open a session for a specific context/namespace/resource, ca (kubel-open "" "" "") ``` +Each kubel buffer will automatically be renamed using the following template: +``` +*kubel session: ||||* +``` + ## Shortcuts On the kubel screen, place your cursor on a resource diff --git a/kubel.el b/kubel.el index 79b6273..204793c 100644 --- a/kubel.el +++ b/kubel.el @@ -245,7 +245,7 @@ off - always assume we cannot list namespaces" (goto-char (point-max)) (insert (format "%s\n" str)))) -(defvar kubel--last-command nil) +(defvar-local kubel--last-command nil) (defun kubel--log-command (process-name cmd) "Log the kubectl command to the process buffer. @@ -266,23 +266,28 @@ CMD is the command string to run." (with-current-buffer standard-output (shell-command cmd t "*kubel stderr*")))) -(defvar kubel-namespace "default" +(defvar-local kubel-namespace "default" "Current namespace.") -(defvar kubel-resource "Pods" +(defvar-local kubel-resource "Pods" "Current resource.") -(defvar kubel-context +(defvar-local kubel-context (replace-regexp-in-string "\n" "" (kubel--exec-to-string "kubectl config current-context")) "Current context. Tries to smart default.") -(defvar kubel-resource-filter "" +(defvar-local kubel-resource-filter "" "Substring filter for resource name.") -(defvar kubel-selector "" +(defvar-local kubel-selector "" "Label selector for resources.") +(defvar-local kubel--line-number nil + "Store the current line number to jump back after a refresh.") + +(defvar-local kubel-last-position nil) + (defvar kubel-namespace-history '() "List of previously used namespaces.") @@ -315,17 +320,17 @@ CMD is the command string to run." "RoleBindings" "Roles")) -(defvar kubel--kubernetes-version-cached nil) +(defvar-local kubel--kubernetes-version-cached nil) (defvar kubel--kubernetes-resources-list-cached nil) -(defvar kubel--can-get-namespace-cached nil) +(defvar-local kubel--can-get-namespace-cached nil) (defvar kubel--namespace-list-cached nil) -(defvar kubel--label-values-cached nil) +(defvar-local kubel--label-values-cached nil) -(defvar kubel--selected-items '()) +(defvar-local kubel--selected-items '()) (defun kubel--invalidate-context-caches () "Invalidate the context caches." @@ -477,12 +482,15 @@ If MAX is the end of the line, dynamically adjust." "Return the width of a specific COLNUM in ENTRYLIST." (seq-max (mapcar (lambda (x) (length (nth colnum x) )) entrylist))) +(defun kubel--buffer-name-from-parameters (context namespace resource) + "Return a preconfigured kubel buffer name." + (concat (format "*kubel session: |%s|%s|%s|*" context namespace resource))) + (defun kubel--buffer-name () "Return kubel buffer name." - (concat (format "*kubel (%s) [%s]: %s" kubel-namespace kubel-context kubel-resource) + (concat (kubel--buffer-name-from-parameters kubel-context kubel-namespace kubel-resource) (unless (equal kubel-selector "") - (format " (%s)" kubel-selector)) - "*")) + (format " (%s)" kubel-selector)))) (defun kubel--items-selected-p () "Return non-nil if there are items selected." @@ -531,7 +539,7 @@ ARGS is a ist of arguments. READONLY If true buffer will be in readonly mode(view-mode)." (when (equal process-name "") (setq process-name "kubel-command")) - (let ((buffer-name (format "*%s*" process-name)) + (let ((buffer-name (format "*kubel resource: |%s|%s|%s|*" kubel-context kubel-namespace (string-join args "_"))) (error-buffer (kubel--process-error-buffer process-name)) (cmd (append (list kubel-kubectl) (kubel--get-context-namespace) args))) (when (get-buffer buffer-name) @@ -656,7 +664,7 @@ TYPENAME is the resource type/name." (equal (capitalize kubel-resource) "Pods")) (defun kubel--is-deployment-view () - "Return non-nil if this is the pod view." + "Return non-nil if this is a deployment view." (-contains? '("Deployments" "deployments" "deployments.apps") kubel-resource)) (defun kubel--is-scalable () @@ -667,12 +675,13 @@ TYPENAME is the resource type/name." (-contains? '("StatefulSets" "statefulsets" "statefulsets.apps") kubel-resource))) ;; interactive +;;;###autoload (define-minor-mode kubel-yaml-editing-mode "Kubel Yaml editing mode. Use C-c C-c to kubectl apply the current yaml buffer." :init-value nil :keymap (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") 'kubel-apply) + (define-key map (kbd "C-c C-c") #'kubel-apply) map)) (defun kubel-apply () @@ -683,7 +692,6 @@ Use C-c C-c to kubectl apply the current yaml buffer." (with-parsed-tramp-file-name default-directory nil (format "/%s%s:%s@%s:" (or hop "") method user host))) "")) - (let* ((filename-without-tramp-prefix (format "/tmp/kubel/%s-%s.yaml" (replace-regexp-in-string "\*\\| " "" (buffer-name)) (floor (float-time)))) @@ -701,15 +709,20 @@ Use C-c C-c to kubectl apply the current yaml buffer." DESCRIBE is the optional param to describe instead of get." (interactive "P") (let* ((resource (kubel--get-resource-under-cursor)) + (ctx kubel-context) + (ns kubel-namespace) + (res kubel-resource) (process-name (format "kubel - %s - %s" kubel-resource resource))) (if describe (kubel--exec process-name (list "describe" kubel-resource (kubel--get-resource-under-cursor))) (kubel--exec process-name (list "get" kubel-resource (kubel--get-resource-under-cursor) "-o" kubel-output))) (when (or (string-equal kubel-output "yaml") (transient-args 'kubel-describe-popup)) (yaml-mode) - (kubel-yaml-editing-mode)) - (goto-char (point-min)))) - + (kubel-yaml-editing-mode) + (setq kubel-context ctx) + (setq kubel-namespace ns) + (setq kubel-resource res) + (goto-char (point-min))))) (defun kubel--default-tail-arg (args) "Ugly function to make sure that there is at least the default tail. @@ -837,10 +850,9 @@ ARGS is the arguments list from transient." (kubel--buffer (get-buffer (kubel--buffer-name))) (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) - (when kubel--buffer (kill-buffer kubel--buffer)) (setq kubel-namespace namespace) (kubel--add-namespace-to-history namespace) - (kubel last-default-directory))) + (kubel-refresh last-default-directory))) (defun kubel-set-context () "Set the context." @@ -854,7 +866,7 @@ ARGS is the arguments list from transient." (when kubel--buffer (kill-buffer kubel--buffer));; kill buffer for previous context if possible (kubel--invalidate-context-caches) (setq kubel-namespace "default") - (kubel last-default-directory))) + (kubel-refresh last-default-directory))) (defun kubel--add-selector-to-history (selector) "Add SELECTOR to history if it isn't there already." @@ -883,8 +895,9 @@ ARGS is the arguments list from transient." (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 - (kubel)) + (kubel--add-selector-to-history kubel-selector) + ;; Update pod list according to the label selector + (kubel-refresh)) (defun kubel--fetch-api-resource-list () "Fetch the API resource list." @@ -907,9 +920,8 @@ the context caches, including the cached resource list." (kubel--buffer (get-buffer current-buffer-name)) (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) (setq kubel-resource - (completing-read "Select resource: " resource-list)) - (when kubel--buffer (kill-buffer kubel--buffer)) ;; kill buffer for previous context if possible - (kubel last-default-directory))) + (completing-read "Select resource: " resource-list)) + (kubel-refresh last-default-directory))) (defun kubel-set-output-format () "Set output format of kubectl." @@ -917,8 +929,9 @@ the context caches, including the cached resource list." (setq kubel-output (completing-read "Set output format: " - '("yaml" "json" "wide" "custom-columns="))) - (kubel)) + (completing-read + "Set output format: " + '("yaml" "json" "wide" "custom-columns="))))) (defun kubel-port-forward-pod (p) "Port forward a pod to your local machine. @@ -1077,7 +1090,7 @@ REPLICAS is the number of desired replicas." FILTER is the filter string." (interactive "MFilter: ") (setq kubel-resource-filter filter) - (kubel)) + (kubel-refresh)) (defun kubel--jump-to-highlight (init search reset) "Base function to jump to highlight. @@ -1138,7 +1151,7 @@ RESET is to be called if the search is nil after the first attempt." (progn (push item kubel--selected-items) (forward-line 1) - (kubel))))) + (kubel-refresh))))) (defun kubel-unmark-item () "Unmark the item under cursor." @@ -1147,7 +1160,7 @@ RESET is to be called if the search is nil after the first attempt." (when (-contains? kubel--selected-items item) (progn (setq kubel--selected-items (delete item kubel--selected-items)) - (kubel))))) + (kubel-refresh))))) (defun kubel-mark-all () "Mark all items." @@ -1158,13 +1171,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)) + (kubel-refresh)) (defun kubel-unmark-all () "Unmark all items." (interactive) (setq kubel--selected-items '()) - (kubel)) + (kubel-refresh)) ;; popups @@ -1216,7 +1229,7 @@ RESET is to be called if the search is nil after the first attempt." ;; global ("RET" "Resource details" kubel-describe-popup) ("E" "Quick edit" kubel-quick-edit) - ("g" "Refresh" kubel) + ("g" "Refresh" kubel-refresh) ("k" "Delete" kubel-delete-popup) ("r" "Rollout" kubel-rollout-history)] ["" ;; based on current view @@ -1253,7 +1266,7 @@ RESET is to be called if the search is nil after the first attempt." (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 "g") 'kubel) + (define-key map (kbd "g") 'kubel-refresh) (define-key map (kbd "h") 'kubel-help-popup) (define-key map (kbd "?") 'kubel-help-popup) (define-key map (kbd "F") 'kubel-set-output-format) @@ -1295,16 +1308,64 @@ DIRECTORY is optional for TRAMP support." (setq kubel-resource (or resource "Pods")) (kubel directory)) + +(defun kubel--current-state () + "Show in the Echo Area the current context, namespace, and resource." + (message (concat + (format "[Context: %s] [Namespace: %s] [Resource: %s]" kubel-context kubel-namespace kubel-resource) + (unless (equal kubel-selector "") + (format " (%s)" kubel-selector))))) + ;;;###autoload -(defun kubel (&optional directory) - "Invoke the kubel buffer. +(defun kubel-refresh (&optional directory) + "Refresh the current kubel buffer, calling kubectl using the configured +context, namespace, and resource. DIRECTORY is optional for TRAMP support." (interactive) - (kubel--pop-to-buffer (kubel--buffer-name)) + (kubel--save-line) (when directory (setq default-directory directory)) - (kubel-mode) - (message (concat "Namespace: " kubel-namespace))) + (let ((name (kubel--buffer-name))) + (unless (get-buffer name) + (rename-buffer name)) + (message (format "Running kubectl for: %s..." name))) + (let ((entries (kubel--populate-list))) + (setq tabulated-list-format (car entries)) + (setq tabulated-list-entries (cadr entries))) ; TODO handle "No resource found" + (setq tabulated-list-sort-key kubel--list-sort-key) + (setq tabulated-list-sort-key nil) + (tabulated-list-init-header) + (tabulated-list-print) + (kubel--current-state) + (kubel--jump-back-to-line)) + +;;;###autoload +(defun kubel-open (context namespace resource &optional directory) + "Create a new kubel buffer using passed parameters CONTEXT NAMESPACE RESOURCE. +DIRECTORY is optional for TRAMP support." + (let ((tmpname "*kubel-tmp*") + (name (kubel--buffer-name-from-parameters context namespace resource))) + (if (get-buffer name) + (pop-to-buffer-same-window name) + (with-current-buffer (get-buffer-create tmpname) + (kubel-mode) + (setq kubel-context context) + (setq kubel-namespace namespace) + (setq kubel-resource resource) + (pop-to-buffer-same-window tmpname) + (kubel-refresh directory))))) + +;;;###autoload +(defun kubel (&optional directory) + "Invoke the kubel buffer. +DIRECTORY is optional for TRAMP support." + (interactive) + (let* ((name (kubel--buffer-name)) + (buf (generate-new-buffer name))) + (switch-to-buffer buf) + (with-current-buffer buf + (kubel-mode) + (kubel-refresh directory)))) (define-derived-mode kubel-mode tabulated-list-mode "Kubel" "Special mode for kubel buffers." @@ -1314,20 +1375,6 @@ DIRECTORY is optional for TRAMP support." (setq mode-name "Kubel") (setq major-mode 'kubel-mode) (use-local-map kubel-mode-map) - (let ((entries (kubel--populate-list))) - (setq tabulated-list-format (car entries)) - (setq tabulated-list-entries (cadr entries))) ; TODO handle "No resource found" - (setq tabulated-list-sort-key kubel--list-sort-key) - (setq tabulated-list-sort-key nil) - (tabulated-list-init-header) - (let ((line-num (line-number-at-pos (point))) - (current-id (tabulated-list-get-id))) - (tabulated-list-print t) - (unless (string-equal current-id (tabulated-list-get-id)) - ;; tabulated-list could not follow the current entry, then fallback on - ;; keeping the same line. - (goto-char (point-min)) - (forward-line (1- line-num)))) (hl-line-mode 1) (run-mode-hooks 'kubel-mode-hook))