Skip to content

Commit

Permalink
Multi buffers support (take abrochard#57 and rebase)
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Sep 9, 2023
1 parent 44ed9ca commit 558c5f3
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 56 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -76,6 +77,11 @@ To programmatically open a session for a specific context/namespace/resource, ca
(kubel-open "<context>" "<namespace>" "<OPTIONAL resource>")
```

Each kubel buffer will automatically be renamed using the following template:
```
*kubel session: |<context>|<namespace>|<resource>|*
```

## Shortcuts

On the kubel screen, place your cursor on a resource
Expand Down
159 changes: 103 additions & 56 deletions kubel.el
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.")

Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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."
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 ()
Expand All @@ -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 ()
Expand All @@ -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))))
Expand All @@ -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.
Expand Down Expand Up @@ -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."
Expand All @@ -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."
Expand Down Expand Up @@ -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."
Expand All @@ -907,18 +920,18 @@ 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."
(interactive)
(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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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."
Expand All @@ -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."
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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."
Expand All @@ -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))

Expand Down

0 comments on commit 558c5f3

Please sign in to comment.