;; Use `kubed-display-pod' to display a Kuberenetes pod,
;; `kubed-edit-pod' to edit it, `kubed-delete-pods' to delete it, and
;; `kubed-list-pods' to see a menu of all pods. You can create new pods
-;; from YAML or JSON files with `kubed-create-pod'. To update the list
-;; of current pods, use `kubed-update-pods' or `kubed-update-all'.
+;; from YAML or JSON files with `kubed-create-pod'.
;;
;; Similar commands are defined for other types of resources as well.
;;
;; current `kubectl' context and namespace. To change your current
;; Kuberenetes context or namespace, use `kubed-use-context' and
;; `kubed-set-namespace'; all resource lists are updated automatically
-;; after you do so. In addition, you can use the minor mode
-;; `kubed-all-namespaces-mode' to see resources from all namespaces.
-;; The prefix keymap `kubed-prefix-map' gives you quick access to these
-;; and other useful commands. You may want to bind it to a convenient
-;; key in your global keymap with `keymap-global-set':
+;; after you do so. The prefix keymap `kubed-prefix-map' gives you
+;; quick access to these and other useful commands, you may want to bind
+;; it globally to a convenient key with `keymap-global-set':
;;
;; (keymap-global-set "C-c k" 'kubed-prefix-map)
;;
"Kubernetes interface."
:group 'tools)
-(defcustom kubed-update-hook nil
- "List of functions that `kubed-update-all' runs."
- :type 'hook)
-
(defcustom kubed-kubectl-program "kubectl"
"Name of `kubectl' executable to use for interacting with Kubernetes."
:type 'string)
:type 'hook)
(defcustom kubed-name-column '("Name" 48 t)
- "Specification of name column in Kubernetes resource list buffers."
+ "Specification of resource name column in Kubernetes resource list buffers."
:type '(list string natnum boolean))
-(defcustom kubed-namespace-column '("Namespace" 12 t)
- "Specification of namespace column in Kubernetes resource list buffers."
- :type '(list string natnum boolean))
-
-;;;###autoload
-(define-minor-mode kubed-all-namespaces-mode
- "Show Kubernetes resources from all namespaces, not just current namespace."
- :global t
- (message "Kubed \"all namespaces\" mode is now %s"
- (if kubed-all-namespaces-mode "ON" "OFF")))
+(defvar kubed--data nil)
+
+(defun kubed--alist (type context namespace)
+ "Return information about resources in CONTEXT of type TYPE in NAMESPACE."
+ (alist-get namespace
+ (alist-get type
+ (alist-get context
+ kubed--data nil nil #'string=)
+ nil nil #'string=)
+ nil nil #'equal))
+
+(gv-define-setter kubed--alist (value type context namespace)
+ `(setf (alist-get ,namespace
+ (alist-get ,type
+ (alist-get ,context
+ kubed--data nil nil #'string=)
+ nil nil #'string=)
+ nil nil #'equal)
+ ,value))
+
+(eval-and-compile (defvar kubed--columns nil))
+
+(defun kubed-update (type context &optional namespace)
+ "Update list of resources of type TYPE in CONTEXT and NAMESPACE."
+ (when (process-live-p (alist-get 'process (kubed--alist type context namespace)))
+ (user-error "Update in progress"))
+ (let* ((out (get-buffer-create (format " *kubed-get-%s*" type)))
+ (err (get-buffer-create (format " *kubed-get-%s-stderr*" type)))
+ (columns (alist-get type kubed--columns nil nil #'string=)))
+ (with-current-buffer out (erase-buffer))
+ (setf (alist-get 'process (kubed--alist type context namespace))
+ (make-process
+ :name (format "*kubed-get-%s*" type)
+ :buffer out
+ :stderr err
+ :command (append
+ (list kubed-kubectl-program "get" type
+ "--context" context
+ (format "--output=custom-columns=%s"
+ (mapconcat #'car columns ",")))
+ (when namespace (list "--namespace" namespace)))
+ :sentinel
+ (lambda (_proc status)
+ (cond
+ ((string= status "finished\n")
+ (let (new offsets eol)
+ (with-current-buffer out
+ (goto-char (point-min))
+ (setq eol (pos-eol))
+ (while (re-search-forward "[^ ]+" eol t)
+ (push (1- (match-beginning 0)) offsets))
+ (setq offsets (nreverse offsets))
+ (forward-char 1)
+ (while (not (eobp))
+ (let ((cols nil)
+ (beg (car offsets))
+ (ends (append (cdr offsets)
+ (list (- (pos-eol) (point))))))
+ (dolist (column columns)
+ (let ((str (string-trim (buffer-substring
+ (+ (point) beg)
+ (+ (point) (car ends))))))
+ (push (if-let ((f (cdr column))) (funcall f str) str)
+ cols)
+ (setq beg (pop ends))))
+ (push (nreverse cols) new))
+ (forward-line 1)))
+ (setf (kubed--alist type context namespace)
+ (list (cons 'resources
+ (mapcar (lambda (c) (list (car c) (apply #'vector c)))
+ new))))
+ (let ((bufs nil))
+ (dolist (buf (buffer-list))
+ (and (equal (buffer-local-value 'kubed-list-type buf) type)
+ (equal (buffer-local-value 'kubed-list-context buf) context)
+ (equal (buffer-local-value 'kubed-list-namespace buf) namespace)
+ (with-current-buffer buf
+ (when (derived-mode-p 'kubed-list-mode)
+ (revert-buffer)
+ (when-let ((win (get-buffer-window)))
+ (set-window-point win (point))
+ (push buf bufs))))))
+ (walk-windows
+ (lambda (win)
+ (let ((buf (window-buffer win)))
+ (when (memq buf bufs)
+ (set-window-point
+ win (with-current-buffer buf (point))))))))
+ (message (format "Updated Kubernetes %S." type))))
+ ((string= status "exited abnormally with code 1\n")
+ (with-current-buffer err
+ (goto-char (point-max))
+ (insert "\n" status))
+ (display-buffer err))))))))
(defvar-local kubed-display-resource-info nil
"Information about Kubernetes resource that current buffer displays.
(defun kubed-display-resource-short-description
(type resource context namespace)
+ "Return short string to use as a label for RESOURCE of type TYPE."
(concat type "/" resource
(when namespace (concat "@" namespace))
(when context (concat "[" context "]"))))
-(defun kubed-namespaced-p (type)
- "Return non-nil if TYPE is a namespaced Kubernetes resource type."
- (member type (kubed-api-resources t)))
+(defun kubed-namespaced-p (type &optional context)
+ "Return non-nil if TYPE is a namespaced resource type in context CONTEXT."
+ (member type (kubed-api-resources context t)))
;;;###autoload
(defun kubed-display-resource
- (type resource context namespace)
+ (type resource context &optional namespace)
"Display Kubernetes RESOURCE of type TYPE in BUFFER."
(interactive
(let* ((type (kubed-read-resource-type "Resource type to display"))
(pos nil)
(find-fn (lambda ()
(save-excursion
- ;; TODO: Wait for refresh to finish if underway.
+ ;; FIXME: Wait for refresh to end if underway.
(goto-char (point-min))
(while (not (or pos (eobp)))
(let ((ent (tabulated-list-get-entry)))
(if (equal context (kubed-current-context))
(if namespace
(cond
- (kubed-all-namespaces-mode
- (funcall list-fn)
- (save-excursion
- (goto-char (point-min))
- (while (not (or pos (eobp)))
- (let ((ent (tabulated-list-get-entry)))
- (if (and (equal name (aref ent 0))
- (equal namespace (aref ent 1)))
- (setq pos (point))
- (forward-line))))))
((equal namespace (kubed-current-namespace))
- (funcall list-fn)
+ (funcall list-fn context namespace)
(funcall find-fn))
(t (user-error "Resource not in current namespace")))
;; Non-namespaced.
- (funcall list-fn)
+ (funcall list-fn context)
(funcall find-fn))
(user-error "Resource not in current context"))
(when pos (goto-char pos)))))
revert-buffer-function #'kubed-display-resource-revert
bookmark-make-record-function #'kubed-display-resource-make-bookmark)))
-;;;###autoload
-(defun kubed-update-all ()
- "Update all Kuberenetes resource lists."
- (interactive)
- (run-hooks 'kubed-update-hook))
-
-(defvar-local kubed-frozen nil
- "Whether the current buffer shows a frozen list of Kuberenetes resources.
-
-If a resources list is frozen, then Kubed does not update it when
-obtaining new information from Kuberenetes.")
-
(defcustom kubed-list-filter-operator-alist
'((= . string=)
(~ . string-match-p))
(defvar-local kubed--list-read-filter-target-buffer nil
"Resource list buffer for which this minibuffer is reading a filter.")
+(defvar-local kubed-list-type nil)
+(defvar-local kubed-list-context nil)
+(defvar-local kubed-list-namespace nil)
+
(defun kubed-list-try-read-filter ()
"Try to read a resource list filter in the minibuffer.
(let* ((buf (current-buffer))
(cols (seq-map #'car tabulated-list-format))
(vals (let ((tmp nil))
- (dolist (ent (let ((kubed-list-filter nil))
- (funcall tabulated-list-entries)))
+ (dolist (ent (alist-get 'resources
+ (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
(let ((i 0))
(dolist (col cols)
(push (aref (cadr ent) i)
(defun kubed-list-context-menu (menu click)
"Extend MENU with common actions on Kubernetes resource at CLICK."
(when (tabulated-list-get-entry (posn-point (event-start click)))
+ (define-key menu [kubed-list-select-resource]
+ '(menu-item "Select" kubed-list-select-resource))
+ (define-key menu [kubed-list-display-resource]
+ '(menu-item "Display" kubed-list-display-resource))
+ (define-key menu [kubed-list-delete]
+ '(menu-item "Delete" kubed-list-delete))
+ (define-key menu [kubed-list-patch]
+ '(menu-item "Patch" kubed-list-patch))
+ (define-key menu [kubed-list-edit]
+ '(menu-item "Edit" kubed-list-edit))
+ (define-key menu [kubed-list-kubectl-command]
+ '(menu-item "Execute `kubectl' command" kubed-list-kubectl-command))
(define-key menu [kubed-list-copy-as-kill]
'(menu-item "Copy name" kubed-list-copy-as-kill)))
menu)
+(defun kubed-list-update (&optional quiet)
+ "Update list of Kubernetes resources.
+
+If optional argument QUIET is non-nil, do not emit a message when
+starting to update. Display a message when the update is done
+regardless of QUIET."
+ (interactive "" kubed-list-mode)
+ (kubed-update kubed-list-type kubed-list-context kubed-list-namespace)
+ (force-mode-line-update)
+ (unless quiet (minibuffer-message (format "Updating Kubernetes %S..." kubed-list-type))))
+
+(defun kubed-list-delete-marked ()
+ "Delete marked Kubernetes resources."
+ (interactive "" kubed-list-mode)
+ (let (delete-list)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (eq (char-after) ?D)
+ (push (tabulated-list-get-id) delete-list))
+ (forward-line)))
+ (if delete-list
+ (when (y-or-n-p (format "Delete %d marked Kubernetes resources?"
+ (length delete-list)))
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (member (tabulated-list-get-id) delete-list)
+ (tabulated-list-put-tag
+ (propertize "K" 'help-echo "Deletion in progress")))
+ (forward-line)))
+ (let ((errb (generate-new-buffer " *kubed-list-delete-marked-stderr*")))
+ (make-process
+ :name "*kubed-list-delete-marked*"
+ :stderr errb
+ :command (append
+ (list kubed-kubectl-program "delete" kubed-list-type)
+ delete-list)
+ :sentinel (lambda (_proc status)
+ (cond
+ ((string= status "finished\n")
+ (message (format "Deleted %d marked Kubernetes resources."
+ (length delete-list)))
+ (kubed-list-update t))
+ ((string= status "exited abnormally with code 1\n")
+ (with-current-buffer errb
+ (goto-char (point-max))
+ (insert "\n" status))
+ (display-buffer errb)))))))
+ (user-error "No Kubernetes resources marked for deletion"))))
+
+(defun kubed-list-display-resource (click)
+ "Display Kubernetes resource at CLICK in another window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-display-resource
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-select-resource (click)
+ "Display Kubernetes resource at CLICK in current window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (switch-to-buffer
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ "*")
+ kubed-list-type resource kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-select-resource-other-window (click)
+ "Display Kubernetes resource at CLICK in other window and select that window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (switch-to-buffer-other-window
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ "*")
+ kubed-list-type resource kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-delete (click)
+ "Delete Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (when (y-or-n-p (format "Delete `%s'?" resource))
+ (kubed-delete-resources kubed-list-type (list resource)
+ kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-patch (click)
+ "Patch Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-patch kubed-list-type resource
+ (kubed-read-patch) kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-edit (click)
+ "Edit Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-edit-resource kubed-list-type resource
+ kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-kubectl-command (click)
+ "Use Kubernetes resource at CLICK as argument for `kubectl' command."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-kubectl-command
+ (kubed-read-kubectl-command
+ "Execute command: "
+ (cons (concat
+ " "
+ (when kubed-list-namespace (concat "-n " kubed-list-namespace " "))
+ (when kubed-list-context (concat "--context " kubed-list-context " "))
+ kubed-list-type " " resource)
+ ;; Put point after "kubectl ".
+ 0)))
+ (user-error "No Kubernetes resource at point")))
+
(defvar-keymap kubed-list-mode-map
:doc "Common keymap for Kubernetes resource list buffers."
+ "RET" #'kubed-list-select-resource
+ "o" #'kubed-list-select-resource-other-window
+ "C-o" #'kubed-list-display-resource
+ "D" #'kubed-list-delete
+ "P" #'kubed-list-patch
+ "x" #'kubed-list-delete-marked
+ "e" #'kubed-list-edit
+ "!" #'kubed-list-kubectl-command
+ "G" #'kubed-list-update
"/" #'kubed-list-set-filter
- "A" #'kubed-all-namespaces-mode
"d" #'kubed-list-mark-for-deletion
"u" #'kubed-list-unmark
"w" #'kubed-list-copy-as-kill
"S-TAB" #'kubed-list-previous-column
"<backtab>" #'kubed-list-previous-column)
+(defun kubed-list-entries ()
+ "`tabulated-list-entries' function for `kubed-list-mode'."
+ (let ((pred (kubed-list-interpret-filter))
+ (ents nil))
+ (dolist (ent (alist-get 'resources (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (when (funcall pred ent) (push ent ents)))
+ (reverse ents)))
+
(define-derived-mode kubed-list-mode tabulated-list-mode "Kubernetes Resources"
"Major mode for listing Kubernetes resources.
(save-excursion
(goto-char (point-min))
(while (not (eobp))
- (when-let ((mark (alist-get (tabulated-list-get-id)
- marks nil nil #'equal)))
- (tabulated-list-put-tag mark))
+ (let ((id (tabulated-list-get-id)))
+ (when-let ((mark (alist-get id marks nil nil #'equal)))
+ (tabulated-list-put-tag mark)))
(forward-line))))))
nil t)
(setq-local truncate-string-ellipsis (propertize ">" 'face 'shadow))
+ (setq tabulated-list-entries #'kubed-list-entries)
(add-hook 'context-menu-functions #'kubed-list-context-menu nil t))
+(defun kubed-delete-resources (type resources context &optional namespace)
+ "Delete Kubernetes RESOURCES of type TYPE."
+ (interactive
+ (let* ((type (kubed-read-resource-type "Resource type to delete"))
+ (context (kubed-current-context))
+ (namespace
+ (when (kubed-namespaced-p type)
+ (or (seq-some
+ (lambda (arg)
+ (when (string-match "--namespace=\\(.+\\)" arg)
+ (match-string 1 arg)))
+ (kubed-transient-args 'kubed-transient-delete))
+ (let ((cur (kubed-current-namespace)))
+ (if current-prefix-arg
+ (kubed-read-namespace "Namespace" cur)
+ cur))))))
+ (list type (kubed-read-resource-name type "Delete" nil t nil namespace)
+ context namespace)))
+ (unless resources (user-error "You didn't specify %s to delete" type))
+ (message (format "Deleting Kubernetes %s `%s'..."
+ type (string-join resources "', `")))
+ (if (zerop (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "delete" type
+ (append (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ resources)))
+ (message (format "Deleting Kubernetes %s `%s'... Done."
+ type (string-join resources "', `")))
+ (error (format "Failed to delete Kubernetes %s `%s'"
+ type (string-join resources "', `")))))
+
+(defmacro kubed--static-if (condition then-form &rest else-forms)
+ "A conditional compilation macro.
+Evaluate CONDITION at macro-expansion time. If it is non-nil, expand
+the macro to THEN-FORM. Otherwise expand it to ELSE-FORMS enclosed in a
+‘progn’ form. ELSE-FORMS may be empty.
+
+This is the same as `static-if' from Emacs 30, defined here for
+compatibility with earlier Emacs versions."
+ (declare (indent 2)
+ (debug (sexp sexp &rest sexp)))
+ (if (eval condition lexical-binding)
+ then-form
+ (cons 'progn else-forms)))
+
+(defun kubed-edit-resource (type resource &optional context namespace)
+ "Edit Kubernetes RESOURCE of type TYPE."
+ (unless (bound-and-true-p server-process) (server-start))
+ (let ((process-environment
+ (cons (kubed--static-if (<= 30 emacs-major-version)
+ (concat "KUBE_EDITOR=" emacsclient-program-name)
+ "KUBE_EDITOR=emacsclient")
+ process-environment)))
+ (apply #'start-process
+ (format "*kubed-%S-edit*" type) nil
+ kubed-kubectl-program "edit" type resource
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))))))
+
;;;###autoload
(defmacro kubed-define-resource (resource &optional properties &rest commands)
"Define Kubernetes RESOURCE with associated PROPERTIES and COMMANDS.
- `kubed-list-RESOURCEs': display a buffer listing all RESOURCEs in the
current namespace. The RESOURCEs list buffer uses a dedicated major
mode, `kubed-RESOURCEs-mode', which is also defined by this macro.
-- `kubed-update-RESOURCEs': update and repopulate RESOURCEs list.
- `kubed-create-RESOURCE': create a RESOURCE from a YAML or a JSON file.
- `kubed-explain-RESOURCEs': show buffer with help about RESOURCEs.
this macro appends the string \" Kubernetes RESOURCE at point.\" to it
to obtain the final docstring; lastly, BODY is the body the command.
Within BODY, the variable RESOURCE is let-bound to the name of the
-RESOURCE at point. If RESOURCE is namespaced, then also the variable
-`k8sns' is let-bound to the namespace of the RESOURCE at point within
-BODY when `kubed-all-namespaces-mode' is enabled. For example, if
-RESOURCE is `pod', the following COMMANDS element defines a command
-`kubed-pods-frob' and binds it to the key \"f\" in
-`kubed-pods-mode-map':
+RESOURCE at point. For example, if RESOURCE is `pod', the following
+COMMANDS element defines a command `kubed-pods-frob' and binds it to the
+key \"f\" in `kubed-pods-mode-map':
(frob \"f\" \"Frobnicate\"
(message \"Preparing...\")
- (frobnicate-pod pod k8sns)
+ (frobnicate-pod pod)
(message \"Done.\"))
By default, this macro assumes that RESOURCE is namespaced. To define a
you omit this keyword argument, the plural form defaults to RESOURCE
followed by \"s\"."
(declare (indent 2))
- (let ((hist-var (intern (format "kubed-%S-history" resource)))
- (plrl-var (intern (format "%Ss" resource)))
+ (let ((plrl-var (intern (format "%Ss" resource)))
(read-fun (intern (format "kubed-read-%S" resource)))
- (read-nms (intern (format "kubed-read-namespaced-%S" resource)))
- (desc-fun (intern (format "kubed-%S-description-buffer" resource)))
- (buf-name (format "*kubed-%S*" resource))
(dsp-name (intern (format "kubed-display-%S" resource)))
(edt-name (intern (format "kubed-edit-%S" resource)))
(crt-name (intern (format "kubed-create-%S" resource)))
(menu-map (intern (format "kubed-%S-menu-map" resource)))
(namespaced t)
(keyword nil)
- list-var ents-var hook-var proc-var frmt-var read-crm sure-fun
- ents-fun buff-fun frmt-fun affx-fun updt-cmd list-cmd expl-cmd
- exec-cmd list-buf out-name err-name dlt-errb dlt-name mod-name
- ctxt-fun crt-spec prf-keys)
+ frmt-var buff-fun list-cmd expl-cmd dlt-name mod-name
+ ctxt-fun crt-spec prf-keys hist-var)
;; Process keyword arguments.
(while (keywordp (car commands))
;; FIXME: Add error for unknown keywords.
(t (pop commands))))
- (setq list-var (intern (format "kubed-%S" plrl-var))
- ents-var (intern (format "kubed--%S-entries" plrl-var))
- hook-var (intern (format "kubed-update-%S-hook" plrl-var))
- proc-var (intern (format "kubed-%S-process" plrl-var))
- frmt-var (intern (format "kubed-%S-columns" plrl-var))
- read-crm (intern (format "kubed-read-%S" plrl-var))
- sure-fun (intern (format "kubed-ensure-%S" plrl-var))
- ents-fun (intern (format "kubed-%S-entries" plrl-var))
+ (setq frmt-var (intern (format "kubed-%S-columns" plrl-var))
+ hist-var (intern (format "kubed-%S-history" plrl-var))
buff-fun (intern (format "kubed-%S-buffer" plrl-var))
- frmt-fun (intern (format "kubed-%S-format" plrl-var))
- affx-fun (intern (format "kubed-%S-affixation" plrl-var))
- updt-cmd (intern (format "kubed-update-%S" plrl-var))
list-cmd (intern (format "kubed-list-%S" plrl-var))
expl-cmd (intern (format "kubed-explain-%S" plrl-var))
- exec-cmd (intern (format "kubed-%S-execute" plrl-var))
- list-buf (format "*kubed-%S*" plrl-var)
- out-name (format " *kubed-get-%S*" plrl-var)
- err-name (format " *kubed-get-%S-stderr*" plrl-var)
- dlt-errb (format " *kubed-%S-execute-stderr*" plrl-var)
dlt-name (intern (format "kubed-delete-%S" plrl-var))
mod-name (intern (format "kubed-%S-mode" plrl-var))
ctxt-fun (intern (format "kubed-%S-context-menu" plrl-var)))
- ;; Extend `commands' with standard commands.
- ;; Commands appear in reverse order in context menu.
- (dolist (c `((patch "P" "Patch"
- (kubed-patch ,(symbol-name plrl-var) ,resource
- (kubed-read-patch)
- . ,(when namespaced '(k8sns))))
- (display "C-o" "Display description of"
- (display-buffer
- (,desc-fun ,resource . ,(when namespaced '(k8sns)))))
- (get-in-other-window
- "o" "Pop to buffer showing description of"
- (switch-to-buffer-other-window
- (,desc-fun ,resource . ,(when namespaced '(k8sns)))))
- (kubectl-command
- "!" "Execute kubectl command with"
- (kubed-kubectl-command
- (kubed-read-kubectl-command
- "Execute command: "
- (cons (concat
- " "
- ,@(when namespaced
- `((when k8sns (concat "-n " k8sns " "))))
- ,(symbol-name resource) " " ,resource)
- 0)))) ; Put point after "kubectl ".
- (delete "D" "Delete"
- ,(if namespaced
- `(if k8sns
- (when (y-or-n-p
- (format ,(concat "Delete Kubernetes "
- (symbol-name resource)
- " `%s' in namespace `%s'?")
- ,resource k8sns))
- (,dlt-name (list (list ,resource k8sns))))
- (when (y-or-n-p
- (format ,(concat "Delete Kubernetes "
- (symbol-name resource)
- " `%s'?")
- ,resource))
- (,dlt-name (list ,resource))))
- `(when (y-or-n-p
- (format ,(concat "Delete Kubernetes "
- (symbol-name resource)
- " `%s'?")
- ,resource))
- (,dlt-name (list ,resource)))))
- (edit "e" "Edit"
- ,(if namespaced
- `(,edt-name ,resource k8sns)
- `(,edt-name ,resource)))
- (get "RET" "Switch to buffer showing description of"
- (switch-to-buffer
- (,desc-fun ,resource . ,(when namespaced '(k8sns)))))))
- (push c commands))
-
+ (setf (alist-get (symbol-name plrl-var) kubed--columns nil nil #'string=)
+ (cons '("NAME:.metadata.name")
+ (mapcar (lambda (p)
+ (cons (concat (upcase (symbol-name (car p)))
+ ":"
+ (cadr p))
+ (nth 4 p)))
+ properties)))
;; Generate code.
`(progn
(defvar ,hist-var nil
,(format "History list for `%S'." read-fun))
- (defvar ,list-var nil
- ,(format "List of Kubernetes resources of type `%S'." resource))
- (defvar ,hook-var nil
- ,(format "List of functions to run after updating `%S'." list-var))
- (defvar ,proc-var nil
- ,(format "Process that updates Kubernetes resources of type `%S'." resource))
-
- (defun ,sure-fun ()
- ,(format "Populate `%S', if not already populated." list-var)
- (unless (or ,list-var (process-live-p ,proc-var)) (,updt-cmd)))
-
- (defun ,updt-cmd (&optional quiet)
- ,(format "Update `%S'.
-If optional argument QUIET is non-nil, do not emit a message when
-starting to update. Display a message when the update is done
-regardless of QUIET."
- list-var)
- (interactive)
- (when (process-live-p ,proc-var) (delete-process ,proc-var))
- (with-current-buffer (get-buffer-create ,out-name)
- (erase-buffer))
- (setq ,proc-var
- (make-process
- :name ,(format "*kubed-get-%S*" plrl-var)
- :buffer ,out-name
- :stderr ,err-name
- :command (list
- kubed-kubectl-program
- "get" ,(format "%S" plrl-var)
- ,@(when namespaced
- `((concat "--all-namespaces="
- (if kubed-all-namespaces-mode "true" "false"))))
- (format "--output=custom-columns=%s"
- (string-join
- (cons "NAME:.metadata.name"
- ,(if namespaced
- `(append
- (when kubed-all-namespaces-mode
- '("NAMESPACE:.metadata.namespace"))
- ',(mapcar (lambda (p)
- (concat (upcase (symbol-name (car p)))
- ":"
- (cadr p)))
- properties))
- `',(mapcar (lambda (p)
- (concat (upcase (symbol-name (car p)))
- ":"
- (cadr p)))
- properties)))
- ",")))
- :sentinel
- (lambda (_proc status)
- (cond
- ((string= status "finished\n")
- (let (new offsets eol)
- (with-current-buffer ,out-name
- (goto-char (point-min))
- (setq eol (pos-eol))
- (while (re-search-forward "[^ ]+" eol t)
- (push (1- (match-beginning 0)) offsets))
- (setq offsets (nreverse offsets))
- (forward-char 1)
- (while (not (eobp))
- (let ((cols nil)
- (beg (car offsets))
- (ends (append (cdr offsets) (list (- (pos-eol) (point))))))
- ,@(let ((read-col
- (lambda (p)
- ;; Fresh list to avoid circles.
- (list `(push ,(if-let ((f (nth 4 p)))
- `(funcall ,f (string-trim (buffer-substring
- (+ (point) beg)
- (+ (point) (car ends)))))
- `(string-trim (buffer-substring
- (+ (point) beg)
- (+ (point) (car ends)))))
- cols)
- '(setq beg (pop ends))))))
- (if namespaced
- ;; Resource is namespaced, generate
- ;; code that is sensitive to
- ;; `kubed-all-namespaces-mode'.
- `((if kubed-all-namespaces-mode
- (progn
- ,@(mapcan
- read-col
- ;; Two nils, one for the
- ;; name column, another
- ;; for the namespace.
- `(nil nil . ,properties)))
- ,@(mapcan read-col `(nil . ,properties))))
- ;; Non-namespaced.
- (mapcan read-col `(nil . ,properties))))
- (push (nreverse cols) new))
- (forward-line 1)))
- (setq ,list-var new
- ,proc-var nil)
- (run-hooks ',hook-var)
- (message ,(format "Updated Kubernetes %S." plrl-var))))
- ((string= status "exited abnormally with code 1\n")
- (with-current-buffer ,err-name
- (goto-char (point-max))
- (insert "\n" status))
- (display-buffer ,err-name))))))
- (unless quiet
- (minibuffer-message ,(format "Updating Kubernetes %S..." plrl-var))))
-
- (defun ,affx-fun (,plrl-var)
- ,(format "Return Kubernetes %s with completion affixations."
- (upcase (symbol-name plrl-var)))
- (let ((max (seq-max (cons 0 (mapcar #'string-width ,plrl-var)))))
- (mapcar (lambda (,resource)
- (list ,resource ""
- (concat (make-string (1+ (- max (string-width ,resource))) ?\s)
- (propertize (or (cadr (assoc ,resource ,list-var)) "")
- 'face 'completions-annotations))))
- ,plrl-var)))
-
- (defun ,read-fun (prompt &optional default multi
- . ,(when namespaced '(k8sns)))
+ (defun ,read-fun (prompt &optional default multi context
+ . ,(when namespaced '(namespace)))
,(format "Prompt with PROMPT for a Kubernetes %S name.
Optional argument DEFAULT is the minibuffer default argument.
Non-nil optional argument MULTI says to read and return a list
of %S, instead of just one." resource plrl-var)
- (minibuffer-with-setup-hook
- #',sure-fun
- (funcall
- (if multi #'completing-read-multiple #'completing-read)
- (format-prompt prompt default)
- ,(if namespaced
- `(if (or (null k8sns) (string= k8sns (kubed-current-namespace)))
- ;; Current namespace.
- (lambda (s p a)
- (if (eq a 'metadata)
- '(metadata
- (category . ,(intern (format "kubernetes-%S" resource)))
- ,@(when properties
- `((affixation-function . ,affx-fun))))
- (while (and (process-live-p ,proc-var)
- (null ,list-var))
- (accept-process-output ,proc-var 1))
- (complete-with-action a ,list-var s p)))
- ;; Different namespace.
- (let ((table 'unset))
- (lambda (s p a)
- (if (eq a 'metadata)
- '(metadata
- (category . ,(intern (format "kubernetes-%S" resource))))
- (when (eq table 'unset)
- (setq table (kubed-resource-names
- ,(symbol-name plrl-var) k8sns)))
- (complete-with-action a table s p)))))
- `(lambda (s p a)
- (if (eq a 'metadata)
- '(metadata
- (category . ,(intern (format "kubernetes-%S" resource)))
- ,@(when properties
- `((affixation-function . ,affx-fun))))
- (while (and (process-live-p ,proc-var)
- (null ,list-var))
- (accept-process-output ,proc-var 1))
- (complete-with-action a ,list-var s p))))
- nil 'confirm nil ',hist-var default)) )
-
- (defun ,read-crm (prompt &optional default)
- ,(format "Prompt with PROMPT for Kubernetes %S names.
-
-Optional argument DEFAULT is the minibuffer default argument." resource)
- (,read-fun prompt default t))
-
- (defun ,desc-fun (,resource . ,(when namespaced '(&optional k8sns)))
- ,(format "Return buffer describing Kubernetes %S %s"
- resource (upcase (symbol-name resource)))
- (kubed-display-resource-in-buffer
- ,buf-name ,(symbol-name plrl-var) ,resource (kubed-current-context)
- . ,(when namespaced '((or k8sns (kubed-current-namespace))))))
-
- ,@(when namespaced
- `((defun ,read-nms (prompt &optional default multi)
- (let* ((choice
- (funcall
- (if multi #'completing-read-multiple #'completing-read)
- (format-prompt prompt default)
- (lambda (s p a)
- (if (eq a 'metadata)
- '(metadata
- (category
- . ,(intern (format "kubernetes-namespaced-%S" resource))))
- (while (and (process-live-p ,proc-var)
- (null ,list-var))
- (accept-process-output ,proc-var 1))
- (complete-with-action a (mapcar (pcase-lambda (`(,name ,space . ,_))
- (concat name " " space))
- ,list-var)
- s p)))
- nil 'confirm nil ',hist-var default))
- (split (mapcar (lambda (c)
- (split-string c " "))
- (ensure-list choice))))
- (if multi split (car split))))))
-
- (defun ,dsp-name (,resource . ,(when namespaced '(&optional k8sns)))
+ (kubed-read-resource-name ,(symbol-name plrl-var) prompt default multi context . ,(when namespaced '(namespace))))
+
+ (defun ,dsp-name (,resource &optional context . ,(when namespaced '(namespace)))
,(format "Display Kubernetes %S %s."
resource (upcase (symbol-name resource)))
(interactive
,(if namespaced
- `(if kubed-all-namespaces-mode
- (,read-nms "Display")
- (list (,read-fun "Display" nil nil
- ;; Consult transient for --namespace.
- (seq-some
- (lambda (arg)
- (when (string-match "--namespace=\\(.+\\)" arg)
- (match-string 1 arg)))
- (kubed-transient-args 'kubed-transient-display)))))
+ `(let ((namespace
+ ;; Consult transient for --namespace.
+ (or (seq-some
+ (lambda (arg)
+ (when (string-match "--namespace=\\(.+\\)" arg)
+ (match-string 1 arg)))
+ (kubed-transient-args 'kubed-transient-display))
+ (and current-prefix-arg (kubed-read-namespace "Namespace" (kubed-current-namespace))))))
+ (list (,read-fun "Display" nil nil nil namespace) nil namespace))
`(list (,read-fun "Display"))))
- (display-buffer (,desc-fun ,resource . ,(when namespaced '(k8sns)))))
+ (kubed-display-resource
+ ,(symbol-name plrl-var) ,resource (or context (kubed-current-context))
+ . ,(when namespaced '(namespace))))
- (add-hook 'kubed-update-hook #',updt-cmd)
-
- ,@(when namespaced
- `((add-hook 'kubed-all-namespaces-mode-hook
- (lambda ()
- (setq ,list-var nil)
- (,updt-cmd t)))))
-
- (defun ,edt-name (,resource . ,(when namespaced '(&optional k8sns)))
- ,(format "Edit Kubernetes %S %s."
- resource (upcase (symbol-name resource)))
+ (defun ,edt-name (,resource &optional context . ,(when namespaced '(namespace)))
+ ,(format "Edit Kubernetes %S %s." resource (upcase (symbol-name resource)))
(interactive ,(if namespaced
- `(if kubed-all-namespaces-mode
- (,read-nms "Edit")
- (list (,read-fun "Edit")))
+ `(let ((namespace (and current-prefix-arg
+ (kubed-read-namespace
+ "Namespace" (kubed-current-namespace)))))
+ (list (,read-fun "Edit" nil nil nil namespace) nil namespace))
`(list (,read-fun "Edit"))))
- (unless (bound-and-true-p server-process) (server-start))
- (let ((process-environment
- (cons ,(if (<= 30 emacs-major-version)
- '(concat "KUBE_EDITOR=" emacsclient-program-name)
- "KUBE_EDITOR=emacsclient")
- process-environment)))
- (start-process ,(format "*kubed-%S-edit*" plrl-var) nil
- kubed-kubectl-program "edit"
- ,(symbol-name resource) ,resource
- . ,(when namespaced
- `((if k8sns
- (concat "--namespace=" k8sns)
- "-o=yaml"))))))
-
- ,(if namespaced
- `(defun ,dlt-name (,plrl-var)
- ,(format "Delete Kubernetes %S %s."
- plrl-var (upcase (symbol-name plrl-var)))
- (interactive (if kubed-all-namespaces-mode
- (,read-nms "Delete" nil t)
- (list (,read-crm "Delete"))))
- (unless ,plrl-var
- (user-error ,(format "You didn't specify %S to delete" plrl-var)))
- (if kubed-all-namespaces-mode
- (pcase-dolist (`(,name ,space) ,plrl-var)
- (message ,(concat "Deleting Kubernetes "
- (symbol-name resource)
- " `%s' in namespace `%s'...")
- name space)
- (if (zerop (call-process
- kubed-kubectl-program nil nil nil
- "delete" "--namespace" space
- ,(symbol-name plrl-var) name))
- (message ,(concat "Deleting Kubernetes "
- (symbol-name resource)
- " `%s' in namespace `%s'... Done.")
- name space)
- (error ,(concat "Failed to delete Kubernetes"
- (symbol-name resource)
- " `%s' in namespace `%s'")
- name space)))
- (message ,(concat "Deleting Kubernetes "
- (symbol-name plrl-var)
- " `%s'...")
- (string-join ,plrl-var "', `"))
- (if (zerop (apply #'call-process
- kubed-kubectl-program nil nil nil
- "delete" ,(symbol-name plrl-var) ,plrl-var))
- (message ,(concat "Deleting Kubernetes "
- (symbol-name plrl-var)
- " `%s'... Done.")
- (string-join ,plrl-var "', `"))
- (error ,(concat "Failed to delete Kubernetes"
- (symbol-name plrl-var)
- " `%s'")
- (string-join ,plrl-var "', `")))))
- `(defun ,dlt-name (,plrl-var)
- ,(format "Delete Kubernetes %S %s." plrl-var
- (upcase (symbol-name plrl-var)))
- (interactive (list (,read-crm "Delete")))
- (unless ,plrl-var
- (user-error ,(format "You didn't specify %S to delete" plrl-var)))
- (message ,(concat "Deleting Kubernetes "
- (symbol-name plrl-var)
- " `%s'...")
- (string-join ,plrl-var "', `"))
- (if (zerop (apply #'call-process
- kubed-kubectl-program nil nil nil
- "delete" ,(symbol-name plrl-var) ,plrl-var))
- (message ,(concat "Deleting Kubernetes "
- (symbol-name plrl-var)
- " `%s'... Done.")
- (string-join ,plrl-var "', `"))
- (error ,(concat "Failed to delete Kubernetes"
- (symbol-name plrl-var)
- " `%s'")
- (string-join ,plrl-var "', `")))))
-
- (defvar-local ,ents-var nil)
-
- (defun ,ents-fun ()
- ,(format "`tabulated-list-entries' function for `%s'." mod-name)
- (let ((pred (kubed-list-interpret-filter))
- (ents nil))
- (dolist (c ,ents-var)
- (let ((ent (list ,(if namespaced
- `(if kubed-all-namespaces-mode
- (concat (car c) " " (cadr c))
- (car c))
- `(car c))
- (apply #'vector c))))
- (when (funcall pred ent)
- (push ent ents))))
- (nreverse ents)))
-
- (defun ,exec-cmd ()
- ,(format "Delete marked Kubernetes %S." plrl-var)
- (interactive "" ,mod-name)
- (let (delete-list)
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (when (eq (char-after) ?D)
- (push (tabulated-list-get-id) delete-list))
- (forward-line)))
- (if delete-list
- (when (y-or-n-p (format ,(concat "Delete %d marked Kubernetes "
- (symbol-name plrl-var) "?")
- (length delete-list)))
- ,@(if namespaced
- `((if kubed-all-namespaces-mode
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (when (member (tabulated-list-get-id) delete-list)
- (tabulated-list-put-tag
- (propertize "K" 'help-echo "Deletion in progress"))
- (let* ((k8sent (tabulated-list-get-entry))
- (name (aref k8sent 0))
- (space (aref k8sent 1)))
- (make-process
- :name ,(format "*kubed-%S-execute*" plrl-var)
- :stderr ,dlt-errb
- :command (list kubed-kubectl-program
- "delete"
- "--namespace" space
- ,(symbol-name plrl-var)
- name)
- :sentinel (lambda (_proc status)
- (cond
- ((string= status "finished\n")
- (message (format ,(concat "Deleted Kubernetes "
- (symbol-name resource)
- " `%s' in namespace `%s'.")
- name space))
- (,updt-cmd t))
- ((string= status "exited abnormally with code 1\n")
- (with-current-buffer ,dlt-errb
- (goto-char (point-max))
- (insert "\n" status))
- (display-buffer ,dlt-errb)))))))
- (forward-line)))
- (save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (when (member (tabulated-list-get-id) delete-list)
- (tabulated-list-put-tag
- (propertize "K" 'help-echo "Deletion in progress")))
- (forward-line)))
- (make-process
- :name ,(format "*kubed-%S-execute*" plrl-var)
- :stderr ,dlt-errb
- :command (append
- (list kubed-kubectl-program
- "delete" ,(symbol-name plrl-var))
- delete-list)
- :sentinel (lambda (_proc status)
- (cond
- ((string= status "finished\n")
- (message (format ,(concat "Deleted %d marked Kubernetes "
- (symbol-name plrl-var) ".")
- (length delete-list)))
- (,updt-cmd t))
- ((string= status "exited abnormally with code 1\n")
- (with-current-buffer ,dlt-errb
- (goto-char (point-max))
- (insert "\n" status))
- (display-buffer ,dlt-errb)))))))
- `((save-excursion
- (goto-char (point-min))
- (while (not (eobp))
- (when (member (tabulated-list-get-id) delete-list)
- (tabulated-list-put-tag
- (propertize "K" 'help-echo "Deletion in progress")))
- (forward-line)))
- (make-process
- :name ,(format "*kubed-%S-execute*" plrl-var)
- :stderr ,dlt-errb
- :command (append
- (list kubed-kubectl-program
- "delete" ,(symbol-name plrl-var))
- delete-list)
- :sentinel (lambda (_proc status)
- (cond
- ((string= status "finished\n")
- (message (format ,(concat "Deleted %d marked Kubernetes "
- (symbol-name plrl-var) ".")
- (length delete-list)))
- (,updt-cmd t))
- ((string= status "exited abnormally with code 1\n")
- (with-current-buffer ,dlt-errb
- (goto-char (point-max))
- (insert "\n" status))
- (display-buffer ,dlt-errb))))))))
- (user-error ,(format "No Kubernetes %S marked for deletion" plrl-var)))))
+ (kubed-edit-resource ,(symbol-name plrl-var) ,resource context
+ . ,(when namespaced '(namespace))))
+
+ (defun ,dlt-name (,plrl-var &optional context
+ . ,(when namespaced '(namespace)))
+ ,(format "Delete Kubernetes %S %s." plrl-var
+ (upcase (symbol-name plrl-var)))
+ (interactive ,(if namespaced
+ `(let ((namespace (and current-prefix-arg
+ (kubed-read-namespace
+ "Namespace" (kubed-current-namespace)))))
+ (list (,read-fun "Delete" nil t nil namespace) nil namespace))
+ `(list (,read-fun "Delete" nil t))))
+ (unless ,plrl-var
+ (user-error ,(format "You didn't specify %S to delete" plrl-var)))
+ (kubed-delete-resources ,(symbol-name plrl-var) ,plrl-var context
+ . ,(when namespaced '(namespace))))
,(if crt-spec `(defun ,crt-name . ,crt-spec)
`(defun ,crt-name (definition)
(interactive (list (kubed-read-resource-definition-file-name
,(symbol-name resource))))
(kubed-create definition ,(symbol-name resource))
- (,updt-cmd t)))
-
- ,@(mapcar
- (pcase-lambda (`(,suffix ,_key ,desc . ,body))
- `(defun ,(intern (format "kubed-%S-%S" plrl-var suffix)) (click)
- ,(format "%s Kubernetes %S at point." desc resource)
- (interactive (list last-nonmenu-event) ,mod-name)
- (if-let ((pos (mouse-set-point click))
- . ,(if namespaced
- `((k8sent (tabulated-list-get-entry pos))
- (,resource (aref k8sent 0)))
- `((,resource (tabulated-list-get-id pos)))))
- ,(if namespaced
- `(let ((k8sns (when kubed-all-namespaces-mode
- (aref (tabulated-list-get-entry pos) 1))))
- ,@body)
- `(progn ,@body))
- (user-error ,(format "No Kubernetes %S at point" resource)))))
- commands)
+ (kubed-list-update t)))
+
+ ,@(let ((click-var (gensym "click")))
+ (mapcar
+ (pcase-lambda (`(,suffix ,_key ,desc . ,body))
+ `(defun ,(intern (format "kubed-%S-%S" plrl-var suffix)) (,click-var)
+ ,(format "%s Kubernetes %S at point." desc resource)
+ (interactive (list last-nonmenu-event) ,mod-name)
+ (if-let ((,resource (tabulated-list-get-id (mouse-set-point ,click-var))))
+ (progn ,@body)
+ (user-error ,(format "No Kubernetes %S at point" resource)))))
+ commands))
(defvar-keymap ,(intern (format "kubed-%S-mode-map" plrl-var))
:doc ,(format "Keymap for `%S" mod-name)
- "G" #',updt-cmd
- "x" #',exec-cmd
- "+" #',crt-name
+ "+" #',crt-name
,@(mapcan
(pcase-lambda (`(,suffix ,key ,_desc . ,_body))
(when key
(caddr p)
(if-let ((sorter (cadddr p)))
`(lambda (l r)
- ,(if namespaced
- `(let ((c (+ ,i (if kubed-all-namespaces-mode 1 0))))
- (funcall ,sorter (aref (cadr l) c) (aref (cadr r) c)))
- `(funcall ,sorter (aref (cadr l) ,i) (aref (cadr r) ,i))))
+ (funcall ,sorter (aref (cadr l) ,i) (aref (cadr r) ,i)))
t))
(nthcdr 5 p))
res))
(reverse res)))
- (defun ,frmt-fun ()
- (apply #'vector
- (cons
- kubed-name-column
- ,(if namespaced
- `(append
- (when kubed-all-namespaces-mode
- (list kubed-namespace-column))
- ,frmt-var)
- frmt-var))))
-
- (defun ,ctxt-fun (menu click)
- (when (tabulated-list-get-entry (posn-point (event-start click)))
- ,@(mapcar
- (pcase-lambda (`(,suffix ,_key ,desc . ,_body))
- `(define-key
- menu [,(intern (format "kubed-%S-%S" plrl-var suffix))]
- (list 'menu-item ,(format "%s this %S" desc resource)
- #',(intern (format "kubed-%S-%S" plrl-var suffix)))))
- (reverse commands)))
+ (defun ,ctxt-fun (menu . ,(if commands '(click) '(_click)))
+ ,@(when commands
+ `((when (tabulated-list-get-entry (posn-point (event-start click)))
+ ,@(mapcar
+ (pcase-lambda (`(,suffix ,_key ,desc . ,_body))
+ `(define-key
+ menu [,(intern (format "kubed-%S-%S" plrl-var suffix))]
+ (list 'menu-item ,(format "%s this %S" desc resource)
+ #',(intern (format "kubed-%S-%S" plrl-var suffix)))))
+ (reverse commands)))))
menu)
(define-derived-mode ,mod-name kubed-list-mode
(list ,(format "Kubernetes %ss" (capitalize (symbol-name resource)))
- (list ',proc-var
- (list :propertize "[...]" 'help-echo "Updating...")
- (list 'kubed-list-filter
- (list :propertize
- (list "[" '(:eval (mapconcat #'prin1-to-string kubed-list-filter " ")) "]")
- 'help-echo "Current filter"))))
+ (list '(:eval (if (process-live-p
+ (alist-get 'process
+ (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (propertize "[...]" 'help-echo "Updating...")
+ (when kubed-list-filter
+ (propertize
+ (concat "[" (mapconcat #'prin1-to-string kubed-list-filter " ") "]")
+ 'help-echo "Current filter"))))))
,(format "Major mode for listing Kubernetes %S." plrl-var)
:interactive nil
(setq kubed-list-filter-history-variable
- ',(intern (format "kubed-%S-filter-history" plrl-var)))
- (setq tabulated-list-format (,frmt-fun))
- (setq tabulated-list-entries #',ents-fun)
- (setq tabulated-list-padding 2)
+ ',(intern (format "kubed-%S-filter-history" plrl-var))
+ kubed-list-type ,(symbol-name plrl-var)
+ tabulated-list-padding 2
+ tabulated-list-format (apply #'vector (cons kubed-name-column ,frmt-var)))
(add-hook 'context-menu-functions #',ctxt-fun nil t)
(tabulated-list-init-header))
- (defun ,buff-fun (,plrl-var &optional buffer frozen)
- (with-current-buffer (or buffer (get-buffer-create ,list-buf))
- (,mod-name)
- (let* ((buf (current-buffer))
- (fun (lambda ()
- (when (buffer-live-p buf)
- (with-current-buffer buf
- (unless kubed-frozen
- (setq ,ents-var ,list-var)
- (setq tabulated-list-format (,frmt-fun))
- (when (kubed-list-validate-filter kubed-list-filter)
- ;; Nullify filter, if no longer valid.
- (setq kubed-list-filter nil))
- (tabulated-list-init-header)
- (revert-buffer)))))))
- (add-hook ',hook-var fun)
- (add-hook 'kill-buffer-hook
- (lambda () (remove-hook ',hook-var fun))
- nil t))
- (setq kubed-frozen frozen)
- (setq ,ents-var ,plrl-var)
- (tabulated-list-print)
- (current-buffer)))
-
- (defun ,list-cmd ()
+ (defun ,buff-fun (context . ,(when namespaced '(namespace)))
+ (let ((buf-name (format ,(format "*Kubed %S%%s*" plrl-var)
+ ,(if namespaced
+ `(concat "@" namespace "[" context "]")
+ `(concat "[" context "]")))))
+ (if-let ((buf (get-buffer buf-name))) buf
+ (with-current-buffer (get-buffer-create buf-name)
+ (,mod-name)
+ (setq kubed-list-context context
+ ,@(when namespaced
+ '(kubed-list-namespace namespace)))
+ (kubed-list-update)
+ (tabulated-list-print)
+ (current-buffer)))))
+
+ (defun ,list-cmd (context . ,(when namespaced '(namespace)))
,(format "List Kubernetes %S." plrl-var)
- (interactive)
- (,sure-fun)
- (pop-to-buffer (,buff-fun ,list-var)))
+ (interactive (list (kubed-current-context)
+ . ,(when namespaced
+ '((let ((cur (kubed-current-namespace)))
+ (if current-prefix-arg
+ (kubed-read-namespace "Namespace" cur)
+ cur))))))
+ (pop-to-buffer (,buff-fun context . ,(when namespaced '(namespace)))))
(defun ,expl-cmd ()
,(format "Show help buffer with explanation about Kubernetes %S." plrl-var)
"e" #',edt-name
"d" #',dlt-name
"RET" #',dsp-name
- "u" #',updt-cmd
"E" #',expl-cmd
,@(mapcan
(pcase-lambda (`(,key ,_label ,cmd))
"<edit>" '("Edit" . ,edt-name)
"<delete>" '("Delete" . ,dlt-name)
"<display>" '("Display" . ,dsp-name)
- "<update>" '("Update" . ,updt-cmd)
"<explain>" '("Explain Fields" . ,expl-cmd)
,@(mapcan
(pcase-lambda (`(,key ,label ,cmd))
"\\)")
1))
-(defmacro kubed--static-if (condition then-form &rest else-forms)
- "A conditional compilation macro.
-Evaluate CONDITION at macro-expansion time. If it is non-nil, expand
-the macro to THEN-FORM. Otherwise expand it to ELSE-FORMS enclosed in a
-‘progn’ form. ELSE-FORMS may be empty.
-
-This is the same as `static-if' from Emacs 30, defined here for
-compatibility with earlier Emacs versions."
- (declare (indent 2)
- (debug (sexp sexp &rest sexp)))
- (if (eval condition lexical-binding)
- then-form
- (cons 'progn else-forms)))
-
;;;###autoload (autoload 'kubed-display-pod "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-pod "kubed" nil t)
;;;###autoload (autoload 'kubed-delete-pods "kubed" nil t)
("A" "Attach" kubed-attach)
("X" "Execute" kubed-exec)
("F" "Forward Port" kubed-forward-port-to-pod))
- (dired "C-d" "Start Dired in home directory of first container of"
+ (dired "C-d" "Start Dired in"
;; Explicit namespace in Kuberenetes remote file names
;; introduced in Emacs 31. See Bug#59797.
(kubed--static-if (<= 31 emacs-major-version)
- (let ((ns (when k8sns (concat "%" k8sns))))
- (dired (concat "/kubernetes:" pod ns ":")))
- (when k8sns
+ (dired (concat "/kubernetes:" pod "%" kubed-list-namespace ":"))
+ (unless (string= kubed-list-namespace (kubed-current-namespace))
(if (y-or-n-p
(format "Starting Dired in a pod in a different namespace \
requires Emacs 31 or later.
You can proceed by first switching your current namespace.
-Switch to namespace `%s' and proceed?" k8sns))
- (kubed-set-namespace k8sns)
+Switch to namespace `%s' and proceed?" kubed-list-namespace))
+ (kubed-set-namespace kubed-list-namespace)
(user-error
"Cannot start Dired in a pod in different namespace `%s'"
- k8sns)))
+ kubed-list-namespace)))
(dired (concat "/kubernetes:" pod ":"))))
- (shell "s" "Start shell in home directory of first container of"
+ (shell "s" "Start shell in"
(kubed--static-if (<= 31 emacs-major-version)
- (let* ((ns (when k8sns (concat "%" k8sns)))
- (default-directory (concat "/kubernetes:" pod ns ":")))
+ (let* ((default-directory (concat "/kubernetes:" pod "%" kubed-list-namespace ":")))
(shell (format "*kubed-pod-%s-shell*" pod)))
- (when k8sns
+ (unless (string= kubed-list-namespace (kubed-current-namespace))
(if (y-or-n-p
(format "Starting Shell in a pod in a different namespace \
requires Emacs 31 or later.
You can proceed by first switching your current namespace.
-Switch to namespace `%s' and proceed?" k8sns))
- (kubed-set-namespace k8sns)
+Switch to namespace `%s' and proceed?" kubed-list-namespace))
+ (kubed-set-namespace kubed-list-namespace)
(user-error
- "Cannot start Dired in a pod in different namespace `%s'"
- k8sns)))
+ "Cannot start Shell in a pod in different namespace `%s'"
+ kubed-list-namespace)))
(let* ((default-directory (concat "/kubernetes:" pod ":")))
(shell (format "*kubed-pod-%s-shell*" pod)))))
(attach "a" "Attach to remote process running on"
- (kubed-attach pod (kubed-read-container pod "Container" t k8sns)
- k8sns t t))
+ (kubed-attach pod (kubed-read-container pod "Container" t
+ kubed-list-context
+ kubed-list-namespace)
+ kubed-list-context kubed-list-namespace t t))
(exec "X" "Execute command in"
- (let ((container (kubed-read-container pod "Container" t k8sns))
+ (let ((container (kubed-read-container pod "Container" t
+ kubed-list-context
+ kubed-list-namespace))
(cmd-args (split-string-and-unquote
(read-string "Execute command: "))))
- (kubed-exec pod (car cmd-args) container k8sns t t (cdr cmd-args))))
+ (kubed-exec pod (car cmd-args) container kubed-list-context
+ kubed-list-namespace t t (cdr cmd-args))))
(logs "l" "Show logs for a container of"
- (kubed-logs pod (kubed-read-container pod "Container" t k8sns)))
+ (kubed-logs pod (kubed-read-container pod "Container" t
+ kubed-list-context kubed-list-namespace)
+ kubed-list-context kubed-list-namespace))
(forward-port "F" "Forward local network port to remote port of"
(let ((local-port (read-number "Forward local port: ")))
(kubed-forward-port-to-pod
pod local-port
(read-number (format "Forward local port %d to remote port: "
local-port))
- k8sns))))
+ kubed-list-context
+ kubed-list-namespace))))
;;;###autoload (autoload 'kubed-display-namespace "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-namespace "kubed" nil t)
:namespaced nil
:prefix (("S" "Set" kubed-set-namespace))
:create
- ((name) "Create Kubernetes namespace with name NAME."
+ ((name &optional context) "Create Kubernetes namespace NAME in CONTEXT."
(interactive (list (read-string "Create namespace with name: ")))
(unless (zerop
- (call-process
- kubed-kubectl-program nil nil nil
- "create" "namespace" name))
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "namespace" name
+ (when context (list "--context" context))))
(user-error "Failed to create Kubernetes namespace with name `%s'" name))
- (message "Created Kubernetes namespace with name `%s'." name)
- (kubed-update-namespaces t))
+ (kubed-update "namespaces" (or context (kubed-current-context)))
+ (message "Created Kubernetes namespace with name `%s'." name))
(set "s" "Set current namespace to"
(save-excursion
(goto-char (point-min))
(when (eq (char-after) ?*)
(tabulated-list-put-tag " "))
(forward-line)))
- (kubed-set-namespace namespace)
+ (kubed-set-namespace namespace kubed-list-context)
(tabulated-list-put-tag
(propertize "*" 'help-echo "Current namespace"))))
(kubed-define-resource secret
((type ".type" 32) (creationtimestamp ".metadata.creationTimestamp" 20)))
-(defun kubed-create-job-from-cronjob (name cronjob &optional namespace)
+(defun kubed-create-job-from-cronjob (name cronjob &optional context namespace)
"Create Kubernetes job with name NAME from cronjob CRONJOB.
-Optional argument NAMESPACE is the namespace to use for the job,
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace to use for the job,
defaulting to the current namespace."
(interactive
(let ((name (read-string "Create job with name: "))
(kubed-transient-args 'kubed-transient-create-job))))
(list name
(kubed-read-cronjob
- (format "Create job `%s' from cronjob" name) nil nil namespace)
- namespace)))
- (unless (zerop
- (apply #'call-process
- kubed-kubectl-program nil nil nil
- "create" "job" name "--from" (concat "cronjob/" cronjob)
- (when namespace (list "-n" namespace))))
- (user-error "Failed to create Kubernetes job `%s'" name))
- (message "Created Kubernetes job `%s'." name)
- (kubed-update-jobs t))
+ (format "Create job `%s' from cronjob" name) nil nil nil namespace)
+ nil namespace)))
+ (let ((context (or context (kubed-current-context)))
+ (namespace (or namespace (kubed-current-namespace context))))
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil nil nil
+ "create" "job" name "--from" (concat "cronjob/" cronjob)
+ "-n" namespace "--context" context))
+ (user-error "Failed to create Kubernetes job `%s'" name))
+ (kubed-update "jobs" context namespace)
+ (message "Created Kubernetes job `%s'." name)))
;;;###autoload (autoload 'kubed-display-job "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-job "kubed" nil t)
((status ".status.conditions[0].type" 10) (starttime ".status.startTime" 20))
:prefix (("c" "Create from Cron" kubed-create-job-from-cronjob))
:create
- ((name image &optional namespace command)
+ ((name image &optional context namespace command)
"Create Kubernetes job with name NAME executing COMMAND in IMAGE.
-Optional argument NAMESPACE is the namespace to use for the job,
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace to use for the job,
defaulting to the current namespace."
(interactive
(let ((name (read-string "Create job with name: "))
- (image nil) (namespace nil) (command nil))
+ (image nil) (context nil) (namespace nil) (command nil))
(dolist (arg (kubed-transient-args 'kubed-transient-create-job))
(cond
((string-match "--image=\\(.+\\)" arg)
(setq image (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
((string-match "-- =\\(.+\\)" arg)
(setq command (split-string-and-unquote (match-string 1 arg))))))
(unless image
(setq image (kubed-read-container-image "Image to run in job")))
- (list name image namespace command)))
+ (list name image context namespace command)))
(unless (zerop
(apply #'call-process
kubed-kubectl-program nil nil nil
"create" "job" name "--image" image
(append
(when namespace (list "-n" namespace))
+ (when context (list "--context" context))
(when command (cons "--" command)))))
(user-error "Failed to create Kubernetes job `%s'" name))
- (message "Created Kubernetes job `%s'." name)
- (kubed-update-jobs t)))
+ (message "Created Kubernetes job `%s'." name)))
;;;###autoload
-(defun kubed-watch-deployment-status (dep &optional namespace)
+(defun kubed-watch-deployment-status (dep &optional context namespace)
"Show and update status of Kubernetes deployment DEP in a dedicate buffer.
-Optional argument NAMESPACE is the namespace of DEP, defaulting to the
-current namespace."
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace."
(interactive
(let ((namespace (seq-some
(lambda (arg)
(when (string-match "--namespace=\\(.+\\)" arg)
(match-string 1 arg)))
(kubed-transient-args 'kubed-transient-rollout))))
- (list (kubed-read-deployment "Watch deployment status" nil nil namespace)
- namespace)))
- (let ((buf (get-buffer-create "*kubed-deployment-status*")))
+ (list (kubed-read-deployment "Watch deployment status" nil nil nil namespace)
+ nil namespace)))
+ (let ((buf (get-buffer-create "*kubed-deployment-status*"))
+ (context (or context (kubed-current-context)))
+ (namespace (or namespace (kubed-current-namespace context))))
(with-current-buffer buf (erase-buffer))
(make-process
:name "*kubed-watch-deployment-status*"
:buffer buf
- :command `(,kubed-kubectl-program "rollout" "status" "deployment" ,dep
- ,@(when namespace `("-n" ,namespace)))
+ :command (list kubed-kubectl-program "rollout" "status"
+ "deployment" dep "-n" namespace "--context" context)
:sentinel
(lambda (_proc status)
(when (member status
'("finished\n" "exited abnormally with code 1\n"))
- (kubed-update-deployments))))
+ (message "Deployment complete")
+ (kubed-update "deployments" context namespace))))
(display-buffer buf)))
(defcustom kubed-restart-deployment-watch-status t
:type 'boolean)
;;;###autoload
-(defun kubed-restart-deployment (dep &optional namespace)
- "Restart Kubernetes deployment DEP in namespace NAMESPACE.
+(defun kubed-restart-deployment (dep &optional context namespace)
+ "Restart Kubernetes deployment DEP in namespace NAMESPACE via CONTEXT.
If NAMESPACE is nil or omitted, it defaults to the current namespace."
(interactive
(let ((namespace (seq-some
(when (string-match "--namespace=\\(.+\\)" arg)
(match-string 1 arg)))
(kubed-transient-args 'kubed-transient-rollout))))
- (list (kubed-read-deployment "Restart deployment" nil nil namespace)
- namespace)))
+ (list (kubed-read-deployment "Restart deployment" nil nil nil namespace)
+ nil namespace)))
(unless (zerop
(apply #'call-process
kubed-kubectl-program nil nil nil
"rollout" "restart" "deployment" dep
- (when namespace (list "-n" namespace))))
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
(user-error "Failed to restart Kubernetes deployment `%s'" dep))
(message "Restarting Kubernetes deployment `%s'." dep)
(when kubed-restart-deployment-watch-status
- (kubed-watch-deployment-status dep namespace)))
+ (kubed-watch-deployment-status dep context namespace)))
;;;###autoload (autoload 'kubed-display-deployment "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-deployment "kubed" nil t)
:prefix (("R" "Restart" kubed-restart-deployment)
("W" "Watch" kubed-watch-deployment-status))
:create
- ((name images &optional namespace replicas port command)
+ ((name images &optional context namespace replicas port command)
"Deploy IMAGES to Kubernetes in deployment with name NAME.
Optional argument NAMESPACE is the namespace to use for the deployment,
(let ((name (read-string "Create deployment with name: "))
(images nil)
(replicas (prefix-numeric-value current-prefix-arg))
- (port nil)
- (command nil)
- (namespace nil))
+ (port nil) (command nil) (context nil) (namespace nil))
(dolist (arg (kubed-transient-args 'kubed-transient-create-deployment))
(cond
((string-match "--replicas=\\(.+\\)" arg)
(setq port (string-to-number (match-string 1 arg))))
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((string-match "-- =\\(.+\\)" arg)
(setq command (split-string-and-unquote (match-string 1 arg))))))
(unless images
(setq images (kubed-read-container-image "Images to deploy" nil t)))
- (list name images namespace replicas port command)))
+ (list name images context namespace replicas port command)))
(unless (zerop
(apply #'call-process
kubed-kubectl-program nil nil nil
(append
(mapcar (lambda (image) (concat "--image=" image)) images)
(when namespace (list (concat "--namespace=" namespace)))
+ (when context (list (concat "--context=" context)))
(when replicas (list (format "--replicas=%d" replicas)))
(when port (list (format "--port=%d" port)))
(when command (cons "--" command)))))
(user-error "Failed to create Kubernetes deployment `%s'" name))
(message "Created Kubernetes deployment `%s'." name)
- (kubed-update-deployments t))
+ (kubed-list-update t))
(restart "R" "Restart"
- (kubed-restart-deployment deployment k8sns)
+ (kubed-restart-deployment deployment kubed-list-context kubed-list-namespace)
(unless kubed-restart-deployment-watch-status
- (kubed-update-deployments t)))
- (watch "W" "Watch" (kubed-watch-deployment-status deployment k8sns)))
+ (message "Deployment restarting")
+ (kubed-list-update t)))
+ (watch "W" "Watch" (kubed-watch-deployment-status
+ deployment kubed-list-context kubed-list-namespace)))
;;;###autoload (autoload 'kubed-display-replicaset "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-replicaset "kubed" nil t)
(ownername ".metadata.ownerReferences[0].name" 16)
(creationtimestamp ".metadata.creationTimestamp" 20)))
-(defun kubed-cronjob-suspended-p (cj &optional ns)
- "Return non-nil if cronjob CJ in namespace NS is currently suspended."
+(defun kubed-cronjob-suspended-p (cj &optional context ns)
+ "Return non-nil if cronjob CJ in CONTEXT and namespace NS is suspended."
(equal (car (apply #'process-lines
kubed-kubectl-program
"get" "cronjobs" cj
"-o" "custom-columns=SUSPENDED:.spec.suspend" "--no-headers"
- (when ns (list "-n" ns))))
+ (append
+ (when ns (list "-n" ns))
+ (when context (list "--context" context)))))
"true"))
;;;###autoload (autoload 'kubed-display-cronjob "kubed" nil t)
(lastsuccess ".status.lastSuccessfulTime" 20)
(activejob ".status.active[0].name" 36))
:create
- ((name image schedule &optional namespace command)
+ ((name image schedule &optional context namespace command)
"Schedule IMAGE to run in a cronjob with name NAME according to SCHEDULE.
Optional argument NAMESPACE is the namespace to use for the cronjob,
(image nil)
(schedule nil)
(command nil)
+ (context nil)
(namespace nil))
(dolist (arg (kubed-transient-args 'kubed-transient-create-cronjob))
(cond
(setq schedule (match-string 1 arg)))
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((string-match "-- =\\(.+\\)" arg)
(setq command (split-string-and-unquote (match-string 1 arg))))))
(unless image
(setq image (kubed-read-container-image "Image to run")))
(unless schedule
(setq schedule (read-string "Cron schedule: " "* * * * *")))
- (list name image schedule namespace command)))
+ (list name image schedule context namespace command)))
(unless (zerop
(apply #'call-process
kubed-kubectl-program nil nil nil
"create" "cronjob" name
"--image" image "--schedule" schedule
(append
+ (when context (list "--context" context))
(when namespace (list "--namespace" namespace))
(when command (cons "--" command)))))
(user-error "Failed to create Kubernetes cronjob `%s'" name))
- (message "Created Kubernetes cronjob `%s'." name)
- (kubed-update-cronjobs t))
+ (message "Created Kubernetes cronjob `%s'." name))
;; Commands in *kubed-cronjobs* buffer.
( toggle-suspension "T" "Toggle suspension of"
(kubed-patch "cronjobs" cronjob
(format
"{\"spec\": {\"suspend\": %s}}"
- (if (kubed-cronjob-suspended-p cronjob k8sns) "false" "true"))
- k8sns)
- (kubed-update-cronjobs t))
+ (if (kubed-cronjob-suspended-p
+ cronjob kubed-list-context kubed-list-namespace)
+ "false" "true"))
+ kubed-list-context kubed-list-namespace))
( create-job "j" "Create job from"
(kubed-create-job-from-cronjob
- (read-string "Create job with name: ") cronjob k8sns)
- (kubed-update-jobs t)
- (kubed-update-cronjobs t)))
+ (read-string "Create job with name: ") cronjob
+ kubed-list-context kubed-list-namespace)))
;;;###autoload (autoload 'kubed-display-ingressclass "kubed" nil t)
;;;###autoload (autoload 'kubed-edit-ingressclass "kubed" nil t)
(creationtimestamp ".metadata.creationTimestamp" 20))
:plural ingresses
:create
- ((name rules &optional namespace class default-backend annotations)
+ ((name rules &optional context namespace class default-backend annotations)
"Create Kubernetes ingress with name NAME and rules RULES.
Optional argument NAMESPACE is the namespace to use for the ingress,
(interactive
(let ((name (read-string "Create ingress with name: "))
(rules nil)
+ (context nil)
(namespace nil)
(class nil)
(annotations nil)
(cond
((string-match "--rule=\\(.+\\)" arg)
(push (match-string 1 arg) rules))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
((string-match "--class=\\(.+\\)" arg)
((string-match "--annotation=\\(.+\\)" arg)
(push (match-string 1 arg) annotations))))
(unless rules (setq rules (kubed-read-ingress-rules)))
- (list name rules namespace class default-backend annotations)))
+ (list name rules context namespace class default-backend annotations)))
(unless (zerop
(apply #'call-process
kubed-kubectl-program nil nil nil
(append
(mapcan (lambda (rule) (list "--rule" rule)) rules)
(when namespace (list "--namespace" namespace))
+ (when context (list "--context" context))
(when class (list "--class" class))
(when default-backend
(list "--default-backend" default-backend))
(mapcan (lambda (ann) (list "--annotation" ann))
annotations))))
(user-error "Failed to create Kubernetes ingress `%s'" name))
- (message "Created Kubernetes ingress `%s'." name)
- (kubed-update-ingresses t)))
+ (message "Created Kubernetes ingress `%s'." name)))
;; TODO: Events may be numerous. Need to only get a few.
;; ;;;###autoload (autoload 'kubed-list-events "kubed" nil t)
kubed-kubectl-program nil nil nil
"config" "use-context" context))
(user-error "Failed to use Kubernetes context `%s'" context))
- (message "Now using Kubernetes context `%s'." context)
- (kubed-update-all))
+ (message "Now using Kubernetes context `%s'." context))
;;;###autoload
(defun kubed-rename-context (old new)
(or context (kubed-current-context))))))
;;;###autoload
-(defun kubed-set-namespace (ns)
- "Set current Kubernetes namespace to NS."
+(defun kubed-set-namespace (ns &optional context)
+ "Set default Kubernetes namespace in CONTEXT to NS."
(interactive
(list (kubed-read-namespace "Set namespace" (kubed-current-namespace))))
(unless (zerop
- (call-process
- kubed-kubectl-program nil nil nil
- "config" "set-context" "--current" "--namespace" ns))
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "config" "set-context" "--current" "--namespace" ns
+ (when context (list "--context" context))))
(user-error "Failed to set Kubernetes namespace to `%s'" ns))
- (message "Kubernetes namespace is now `%s'." ns)
- (kubed-update-all))
+ (message "Kubernetes namespace is now `%s'." ns))
(defcustom kubed-read-resource-definition-filter-files-by-kind t
"Whether to filter file completion candidates by their Kubernetes \"kind\".
'("yaml" "yml" "json"))))))))
;;;###autoload
-(defun kubed-apply (config &optional kind)
- "Apply CONFIG to Kubernetes resource of kind KIND."
+(defun kubed-apply (config &optional kind context)
+ "Apply CONFIG to Kubernetes resource of kind KIND via CONTEXT."
(interactive
(list (or (seq-some
(lambda (arg)
(kubed-read-resource-definition-file-name))))
(let ((kind (or kind "resource")))
(message "Applying Kubernetes %s configuration `%s'..." kind config)
- (call-process kubed-kubectl-program nil nil nil
- "apply" "-f" (expand-file-name config))
+ (apply #'call-process kubed-kubectl-program nil nil nil
+ "apply" "-f" (expand-file-name config)
+ (when context (list "--context" context)))
(message "Applying Kubernetes %s configuration `%s'... Done." kind config)))
;;;###autoload
-(defun kubed-create (definition &optional kind)
- "Create Kubernetes resource of kind KIND with definition DEFINITION."
+(defun kubed-create (definition &optional kind context)
+ "Create resource of kind KIND with definition DEFINITION via CONTEXT."
(interactive
(list (or (seq-some
(lambda (arg)
(message "Creating Kubernetes %s with definition `%s'..." kind definition)
(message "Creating Kubernetes %s with definition `%s'... Done. New %s name is `%s'."
kind definition kind
- (car (process-lines kubed-kubectl-program
- "create" "-f" (expand-file-name definition)
- "-o" "jsonpath={.metadata.name}")))))
+ (car (apply #'process-lines kubed-kubectl-program
+ "create" "-f" (expand-file-name definition)
+ "-o" "jsonpath={.metadata.name}"
+ (when context (list "--context" context)))))))
;;;###autoload
(defun kubed-run
- (pod image &optional namespace port attach stdin tty rm envs command args)
+ (pod image &optional context namespace port attach stdin tty rm envs command args)
"Run IMAGE in Kubernetes POD.
Optional argument NAMESPACE is the namespace to use for the created pod,
providing it with arguments."
(interactive
(let ((pod (read-string "Run image in pod with name: "))
- (image nil) (port nil) (namespace nil) (attach nil) (stdin nil)
- (tty nil) (rm nil) (envs nil) (command nil) (args nil))
+ (image nil) (port nil) (context nil) (namespace nil)
+ (attach nil) (stdin nil) (tty nil) (rm nil) (envs nil)
+ (command nil) (args nil))
(dolist (arg (kubed-transient-args 'kubed-transient-run))
(cond
((string-match "--image=\\(.+\\)" arg)
(setq port (string-to-number (match-string 1 arg))))
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((equal "--attach" arg) (setq attach t))
((equal "--stdin" arg) (setq stdin t))
((equal "--tty" arg) (setq tty t))
(setq args (split-string-and-unquote (match-string 1 arg))))))
(unless image
(setq image (read-string "Image to run: " nil 'kubed-container-image-history)))
- (list pod image namespace port attach stdin tty rm envs command args)))
+ (list pod image context namespace port attach stdin tty rm envs command args)))
(if attach
(pop-to-buffer
(apply #'make-comint "kubed-run" kubed-kubectl-program nil
(append
(mapcar (lambda (env) (concat "--env=" env)) envs)
(when namespace (list (concat "--namespace=" namespace)))
+ (when context (list (concat "--context=" context)))
(when stdin '("-i"))
(cond
(attach '("--attach"))
(when command '("--command"))
(when args (cons "--" args)))))
(user-error "Failed to run image `%s'" image))
- (message "Image `%s' is now running in pod `%s'." image pod))
- (kubed-update-pods t))
+ (message "Image `%s' is now running in pod `%s'." image pod)))
-(defun kubed-pod-containers (pod &optional k8sns)
- "Return list of containers in Kubernetes pod POD in namespace K8SNS."
+(defun kubed-pod-containers (pod &optional context namespace)
+ "Return list of containers in Kubernetes pod POD in NAMESPACE in CONTEXT."
(string-split
- (car (process-lines
- kubed-kubectl-program "get"
- (if k8sns (concat "--namespace=" k8sns) "--all-namespaces=false")
- "pod" pod "-o" "jsonpath={.spec.containers[*].name}"))
+ (car (apply #'process-lines
+ kubed-kubectl-program "get"
+ "pod" pod "-o" "jsonpath={.spec.containers[*].name}"
+ (append
+ (when namespace (list "--namespace" namespace))
+ (when context (list "--context" context)))))
" "))
-(defun kubed-pod-default-container (pod &optional k8sns)
- "Return default container of Kubernetes pod POD in namespace K8SNS, or nil."
- (car (process-lines
- kubed-kubectl-program
- "get"
- (if k8sns (concat "--namespace=" k8sns) "--all-namespaces=false")
- "pod" pod "-o"
- "jsonpath={.metadata.annotations.kubectl\\.kubernetes\\.io/default-container}")))
+(defun kubed-pod-default-container (pod &optional context namespace)
+ "Return default container of Kubernetes pod POD in NAMESPACE in CONTEXT."
+ (car (apply #'process-lines
+ kubed-kubectl-program
+ "get" "pod" pod "-o"
+ "jsonpath={.metadata.annotations.kubectl\\.kubernetes\\.io/default-container}"
+ (append
+ (when namespace (list "--namespace" namespace))
+ (when context (list "--context" context))))))
-(defun kubed-read-container (pod prompt &optional guess k8sns)
+(defun kubed-read-container
+ (pod prompt &optional guess context namespace)
"Prompt with PROMPT for a container in POD and return its name.
Non-nil optional argument GUESS says to try and guess which container to
\"kubectl.kubernetes.id/default-container\" annotation, use the
container that this annotation specifes; if there's just one container,
use it; otherwise, fall back to prompting."
- (let ((default (kubed-pod-default-container pod k8sns))
+ (let ((default (kubed-pod-default-container pod context namespace))
(all 'unset))
(or
;; There's a default container, so that's our guess.
(and guess default)
;; No default, but we're allowed to guess, so check if there's just
;; one container, and if so that's our guess.
- (and guess (setq all (kubed-pod-containers pod k8sns))
+ (and guess (setq all (kubed-pod-containers pod context namespace))
(null (cdr all))
(car all))
;; No guessing, prompt.
(completion-table-dynamic
(lambda (_)
(if (eq all 'unset)
- (setq all (kubed-pod-containers pod k8sns))
+ (setq all (kubed-pod-containers pod context namespace))
all)))
nil t nil nil default))))
;;;###autoload
-(defun kubed-logs (pod container &optional k8sns)
+(defun kubed-logs (pod container context namespace)
"Show logs for container CONTAINER in Kubernetes pod POD."
(interactive
- (if kubed-all-namespaces-mode
- (let* ((p-s (kubed-read-namespaced-pod "Show logs for pod"))
- (p (car p-s))
- (s (cadr p-s)))
- (list p (kubed-read-container p "Container" nil s) s))
- (let* ((p (kubed-read-pod "Show logs for pod"))
- (c (kubed-read-container p "Container")))
- (list p c))))
- (let ((buf (generate-new-buffer (format "*kubed-logs %s[%s] in %s*" pod container
- (or k8sns "current namespace")))))
+ (let* ((context (kubed-current-context))
+ (n (kubed-current-namespace context))
+ (n (if current-prefix-arg (kubed-read-namespace "Namespace" n nil context) n))
+ (p (kubed-read-pod "Show logs for pod" nil nil context n))
+ (c (kubed-read-container p "Container" nil context n)))
+ (list p c context n)))
+ (let ((buf (generate-new-buffer
+ (format "*kubed-logs %s[%s] in %s*" pod container namespace))))
(with-current-buffer buf (run-hooks 'kubed-logs-setup-hook))
- (if k8sns
- (message "Getting logs for container `%s' in pod `%s' in namespace `%s'..." container pod k8sns)
- (message "Getting logs for container `%s' in pod `%s'..." container pod))
- (start-process "*kubed-logs*" buf
- kubed-kubectl-program "logs"
- (if k8sns (concat "--namespace=" k8sns) "--tail=-1")
- "-f" "-c" container pod)
+ (message "Getting logs for container `%s' in pod `%s' in namespace `%s'..." container pod namespace)
+ (apply #'start-process
+ "*kubed-logs*" buf
+ kubed-kubectl-program "logs"
+ "-f" "-c" container pod
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))))
(display-buffer buf)))
(defvar kubed-port-forward-process-alist nil
kubed-port-forward-process-alist)))
;;;###autoload
-(defun kubed-forward-port-to-pod (pod local-port remote-port &optional k8sns)
+(defun kubed-forward-port-to-pod
+ (pod local-port remote-port context namespace)
"Forward LOCAL-PORT to REMOTE-PORT of Kubernetes pod POD."
(interactive
- (if kubed-all-namespaces-mode
- (let* ((p-s (kubed-read-namespaced-pod "Forward port to pod"))
- (p (car p-s))
- (s (cadr p-s)))
- (list p (read-number "Local port: ") (read-number "Remote port: ") s))
- (let* ((p (kubed-read-pod "Forward port to pod"))
- (l (read-number "Local port: "))
- (r (read-number "Remote port: ")))
- (list p l r))))
- (if k8sns
- (message "Forwarding local port %d to remote port %d of pod `%s'..."
- local-port remote-port pod)
- (message "Forwarding local port %d to remote port %d of pod `%s' in namespace `%s'..."
- local-port remote-port pod k8sns))
+ (let* ((c (kubed-current-context))
+ (n (kubed-current-namespace c))
+ (n (if current-prefix-arg (kubed-read-namespace "Namespace" n nil c) n))
+ (p (kubed-read-pod "Forward port to pod" nil nil c n))
+ (l (read-number "Local port: "))
+ (r (read-number "Remote port: ")))
+ (list p l r c n)))
+ (message "Forwarding local port %d to remote port %d of pod `%s' in namespace `%s'..."
+ local-port remote-port pod namespace)
(push
(cons
- (format "pod %s %d:%d%s"
- pod local-port remote-port
- (if k8sns (concat " in " k8sns) ""))
- (start-process "*kubed-port-forward*" nil
- kubed-kubectl-program "port-forward"
- (if k8sns (concat "--namespace=" k8sns) "--address=localhost")
- pod (format "%d:%d" local-port remote-port)))
+ (format "pod %s %d:%d in %s" pod local-port remote-port namespace)
+ (apply #'start-process
+ "*kubed-port-forward*" nil
+ kubed-kubectl-program "port-forward"
+ pod (format "%d:%d" local-port remote-port)
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
kubed-port-forward-process-alist))
(defun kubed-stop-port-forward (descriptor)
(transient-args prefix))))
;;;###autoload
-(defun kubed-attach (pod &optional container namespace stdin tty)
+(defun kubed-attach (pod &optional container context namespace stdin tty)
"Attach to running process in CONTAINER in Kubernetes POD.
Optional argument NAMESPACE is the namespace in which to look for POD.
with \\[universal-argument] \\[universal-argument]; and TTY is t unless\
you call this command with \\[universal-argument]."
(interactive
- (let ((namespace nil) (stdin t) (tty t))
+ (let ((context nil) (namespace nil) (stdin t) (tty t))
(when (<= 4 (prefix-numeric-value current-prefix-arg)) (setq tty nil))
(when (<= 16 (prefix-numeric-value current-prefix-arg)) (setq stdin nil))
(dolist (arg (kubed-transient-args 'kubed-transient-attach))
(cond
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((equal "--stdin" arg) (setq stdin t))
((equal "--tty" arg) (setq tty t))))
- (if (and kubed-all-namespaces-mode (not namespace))
- (let* ((p-s (kubed-read-namespaced-pod "Attach to pod"))
- (p (car p-s))
- (s (cadr p-s)))
- (list p (kubed-read-container p "Container" t s) s stdin tty))
- (let* ((p (kubed-read-pod "Attach to pod" nil nil namespace))
- (c (kubed-read-container p "Container" t namespace)))
- (list p c namespace stdin tty)))))
+ (let* ((c (or context (kubed-current-context)))
+ (n (or namespace
+ (when current-prefix-arg
+ (kubed-read-namespace "Namespace" (kubed-current-namespace) nil c))))
+ (p (kubed-read-pod "Attach to pod" nil nil c n)))
+ (list p (kubed-read-container p "Container" t c n) c n stdin tty))))
(pop-to-buffer
(apply #'make-comint "kubed-attach" kubed-kubectl-program nil
"attach" pod
(append
(when namespace (list "-n" namespace))
+ (when context (list "--context" context))
(when container (list "-c" container))
(when stdin '("-i"))
(when tty '("-t"))))))
;;;###autoload
-(defun kubed-diff (definition &optional include-managed)
+(defun kubed-diff (definition &optional include-managed context)
"Display difference between Kubernetes resource DEFINITION and current state.
-Non-nil optional argument INCLUDE-MANAGED (interactively, the prefix
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; non-nil INCLUDE-MANAGED (interactively, the prefix
argument) says to include managed fields in the comparison."
(interactive
- (let ((definition nil) (include-managed nil))
+ (let ((definition nil) (context nil) (include-managed nil))
(dolist (arg (when (and (fboundp 'transient-args)
(fboundp 'kubed-transient-diff))
(transient-args 'kubed-transient-diff)))
(cond
((string-match "--filename=\\(.+\\)" arg)
(setq definition (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((equal "--show-managed-fields" arg) (setq include-managed t))))
(list (or definition (kubed-read-resource-definition-file-name))
- (or include-managed current-prefix-arg))))
+ (or include-managed current-prefix-arg)
+ context)))
(let ((buf (get-buffer-create "*kubed-diff*")))
(with-current-buffer buf
(setq buffer-read-only nil)
(delete-region (point-min) (point-max))
(fundamental-mode)
- (call-process kubed-kubectl-program nil t nil "diff"
- (concat "--show-managed-fields="
- (if include-managed "true" "false"))
- "-f" (expand-file-name definition))
+ (apply #'call-process kubed-kubectl-program nil t nil "diff"
+ (concat "--show-managed-fields="
+ (if include-managed "true" "false"))
+ "-f" (expand-file-name definition)
+ (when context (list "--context" context)))
(setq buffer-read-only t)
(diff-mode)
(goto-char (point-min)))
(display-buffer buf)))
;;;###autoload
-(defun kubed-exec (pod command &optional container namespace stdin tty args)
+(defun kubed-exec
+ (pod command &optional container context namespace stdin tty args)
"Execute COMMAND with ARGS in CONTAINER in Kubernetes POD.
Optional argument NAMESPACE is the namespace in which to look for POD.
with \\[universal-argument] \\[universal-argument]; and TTY is t unless\
you call this command with \\[universal-argument]."
(interactive
- (let ((namespace nil) (stdin t) (tty t) (command nil) (args nil))
+ (let ((context nil) (namespace nil) (stdin t) (tty t) (command nil) (args nil))
(when (<= 4 (prefix-numeric-value current-prefix-arg)) (setq tty nil))
(when (<= 16 (prefix-numeric-value current-prefix-arg)) (setq stdin nil))
(dolist (arg (kubed-transient-args 'kubed-transient-exec))
(cond
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((equal "--stdin" arg) (setq stdin t))
((equal "--tty" arg) (setq tty t))
((string-match "-- =\\(.+\\)" arg)
(setq args (split-string-and-unquote (match-string 1 arg))
command (car args)
args (cdr args)))))
- (if (and kubed-all-namespaces-mode (not namespace))
- (let* ((p-s (kubed-read-namespaced-pod "Execute command in pod"))
- (p (car p-s))
- (s (cadr p-s))
- (c (kubed-read-container p "Container" t s)))
- (unless command
- (setq args (split-string-and-unquote
- (read-string "Execute command: "))
- command (car args)
- args (cdr args)))
- (list p command c s stdin tty args))
- (let* ((p (kubed-read-pod "Execute command in pod" nil nil namespace))
- (c (kubed-read-container p "Container" t namespace)))
- (unless command
- (setq args (split-string-and-unquote
- (read-string "Execute command: "))
- command (car args)
- args (cdr args)))
- (list p command c namespace stdin tty args)))))
+ (let* ((context (or context (kubed-current-context)))
+ (n (or namespace
+ (when current-prefix-arg
+ (kubed-read-namespace "Namespace" (kubed-current-namespace) nil context))))
+ (p (kubed-read-pod "Attach to pod" nil nil context n))
+ (c (kubed-read-container p "Container" t context n)))
+ (unless command
+ (setq args (split-string-and-unquote
+ (read-string "Execute command: "))
+ command (car args)
+ args (cdr args)))
+ (list p command c context n stdin tty args))))
(pop-to-buffer
(apply #'make-comint "kubed-exec" kubed-kubectl-program nil
"exec" pod
(append
(when namespace (list "-n" namespace))
+ (when context (list "--context" context))
(when container (list "-c" container))
(when stdin '("-i"))
(when tty '("-t"))
(read-string "Patch (JSON or YAML): " nil 'kubed-patch-history))
;;;###autoload
-(defun kubed-patch (type name patch &optional namespace strategy)
+(defun kubed-patch (type name patch &optional context namespace strategy)
"Patch Kubernetes resource NAME of TYPE with patch PATCH.
Optional argument NAMESPACE is the namespace in which to look for NAME.
Interactively, prompt for TYPE, NAME and PATCH."
(interactive
(let ((type (kubed-read-resource-type "Resource type to patch"))
- (namespace nil) (strategy nil))
+ (context nil) (namespace nil) (strategy nil))
(dolist (arg (kubed-transient-args 'kubed-transient-apply))
(cond
((string-match "--namespace=\\(.+\\)" arg)
(setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
((string-match "--type=\\(.+\\)" arg)
(setq strategy (match-string 1 arg)))))
(list type
(kubed-read-resource-name type "Resource to patch")
- (kubed-read-patch) namespace strategy)))
+ (kubed-read-patch) context namespace strategy)))
(message "Applying patch to `%s'..." name)
(unless (zerop
(apply #'call-process
"patch" type name "-p" patch
(append
(when namespace (list "-n" namespace))
+ (when context (list "--context" context))
(when strategy (list "--type" strategy)))))
(user-error "Patching `%s' failed" name))
(message "Applying patch to `%s'... Done." name))
(defvar kubed-resource-field-history nil
"Minibuffer history for `kubed-read-resource-field'.")
-(defun kubed-api-resources (&optional only-namespaced)
- "Return list of resource types in the current Kubernetes context.
+(defun kubed-api-resources (&optional context only-namespaced)
+ "Return list of resource types in the context CONTEXT.
Non-nil optional argument ONLY-NAMESPACED says to return only namespaced
resource types."
(apply #'process-lines
kubed-kubectl-program
"api-resources" "--no-headers"
- (when only-namespaced
- '("--namespaced=true")))))
-
-(defun kubed-resource-names (type &optional namespace)
- "Return list of Kuberenetes resource names of type TYPE in NAMESPACE."
- (apply #'process-lines
- kubed-kubectl-program "get" type
- "-o" "custom-columns=NAME:.metadata.name" "--no-headers"
- (when namespace (list "-n" namespace))))
-
-(defun kubed-read-resource-name (type prompt &optional default namespace)
+ (append
+ (when only-namespaced '("--namespaced=true"))
+ (when context (list (concat "--context=" context)))))))
+
+(defun kubed-resource-names (type &optional context namespace)
+ "Return list of Kuberenetes resources of type TYPE in NAMESPACE via CONTEXT."
+ (let ((context (or context (kubed-current-context)))
+ (namespace (or namespace (kubed-current-namespace context))))
+ (unless (kubed--alist type context namespace)
+ (let ((proc (kubed-update type context namespace)))
+ (while (process-live-p proc)
+ (accept-process-output proc 1))))
+ (mapcar #'car (alist-get 'resources (kubed--alist type context namespace)))))
+
+(defun kubed-read-resource-name (type prompt &optional default multi context namespace)
"Prompt with PROMPT for Kubernetes resource name of type TYPE.
Optional argument DEFAULT is the minibuffer default argument. Non-nil
optional argument NAMESPACE says to use names from NAMESPACE as
completion candidates instead of the current namespace."
- (completing-read
+ (funcall
+ (if multi #'completing-read-multiple #'completing-read)
(format-prompt prompt default)
- (kubed-resource-names type namespace)
- nil 'confirm nil nil default))
-
-(defun kubed-read-resource-type (prompt &optional default)
- "Prompt with PROMPT for Kubernetes resource type.
+ (let ((table 'unset))
+ (lambda (s p a)
+ (if (eq a 'metadata)
+ `(metadata (category . ,(intern (concat "kubernetes-" type))))
+ (when (eq table 'unset)
+ (setq table (kubed-resource-names type context namespace)))
+ (complete-with-action a table s p))))
+ nil 'confirm nil (intern (concat "kubed-" type "-history")) default))
+
+(defun kubed-read-resource-type (prompt &optional default context)
+ "Prompt with PROMPT for Kubernetes resource type in context CONTEXT.
Optional argument DEFAULT is the minibuffer default argument."
(completing-read
(format-prompt prompt default)
- (kubed-api-resources)
+ (kubed-api-resources context)
nil 'confirm nil nil default))
-(defun kubed-read-resource-field (prompt &optional default)
+(defun kubed-read-resource-field (prompt &optional default context)
"Prompt with PROMPT for Kubernetes resource type or field name.
-Optional argument DEFAULT is the minibuffer default argument."
+Optional argument DEFAULT is the minibuffer default argument, and
+CONTEXT is the `kubectl' context to use."
(completing-read
(format-prompt prompt default)
(lambda (s p a)
(let ((table
(if (zerop start)
;; Complete resource type.
- (kubed-api-resources)
+ (kubed-api-resources context)
;; Complete (sub-)field name.
(with-temp-buffer
(call-process
"i" 'kubed-ingress-prefix-map
"c" 'kubed-cronjob-prefix-map
"C" #'kubed-use-context
- "U" #'kubed-update-all
- "A" #'kubed-all-namespaces-mode
"+" #'kubed-create
"*" #'kubed-apply
"R" #'kubed-run
"<patch>" '("Patch Resource" . kubed-patch)
"<diff>" '("Diff Config with Live" . kubed-diff)
"<kubectl-command>" '("Invoke kubectl" . kubed-kubectl-command)
- "<all-namespaces>" '("Toggle Namespacing" . kubed-all-namespaces-mode)
- "<update-all>" '("Update Resource Lists" . kubed-update-all)
"<explain>" '("Explain Type or Field" . kubed-explain)
"<use-context>" '("Set Current Context" . kubed-use-context)
"<run>" '("Run Image" . kubed-run)