]> git.eshelyaron.com Git - kubed.git/commitdiff
Major cleanup: simplify and extend resource lists
authorEshel Yaron <me@eshelyaron.com>
Mon, 5 Aug 2024 10:27:27 +0000 (12:27 +0200)
committerEshel Yaron <me@eshelyaron.com>
Mon, 5 Aug 2024 13:39:12 +0000 (15:39 +0200)
This is a big refactor that does the following:

- Significantly simplify 'kubed-define-resource', use common
  type-generic code in runtime instead of generating duplicate code
  for each type.

- Support multiple list buffers with per-buffer namespace
  and context.

- Drop 'kubed-all-namespaces-mode', at least for now.

kubed-transient.el
kubed.el
kubed.texi

index b3f1e3dc4abd487bb52ff532173fcf8dc2912e4d..54b137871ce9bd23094e1fb1d73064180b1c4ed9 100644 (file)
     ("-f" "Definition file" "--filename="
      :reader kubed-transient-read-resource-definition-file-name)]
    ["Kinds"
-    ("d" "deployment" kubed-transient-create-deployment)
-    ("n" "namespace" kubed-create-namespace)
-    ("c" "cronjob" kubed-transient-create-cronjob)
-    ("j" "job" kubed-transient-create-job)
-    ("i" "ingress" kubed-transient-create-ingress)]]
+    ("d" "Deployment" kubed-transient-create-deployment)
+    ("n" "Namespace" kubed-create-namespace)
+    ("c" "CronJob" kubed-transient-create-cronjob)
+    ("j" "Job" kubed-transient-create-job)
+    ("i" "Ingress" kubed-transient-create-ingress)]]
   (interactive)
   (transient-setup 'kubed-transient-create nil nil
                    :scope '("create")))
index 1b10b5a2587d8b8219be38fd2ddad08841b91239..9305d91ba3ff1868b8b984f3df9fbb9725f8d585 100644 (file)
--- a/kubed.el
+++ b/kubed.el
@@ -17,8 +17,7 @@
 ;; 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)
@@ -67,19 +60,103 @@ by default it is `yaml-ts-mode'."
   :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.
@@ -127,17 +204,18 @@ the namespace of the resource, or nil if TYPE is not namespaced.")
 
 (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"))
@@ -209,7 +287,7 @@ the namespace of the resource, or nil if TYPE is not namespaced.")
            (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)))
@@ -219,22 +297,12 @@ the namespace of the resource, or nil if TYPE is not namespaced.")
       (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)))))
@@ -263,18 +331,6 @@ should only be available in buffers that display Kuberenetes resources."
      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))
@@ -374,6 +430,10 @@ If FILTER is omitted or nil, it defaults to `kubed-list-filter'."
 (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.
 
@@ -417,8 +477,10 @@ of the error, push a mark before moving point."
   (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)
@@ -563,14 +625,160 @@ to 1."
 (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
@@ -580,6 +788,16 @@ to 1."
   "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.
 
@@ -602,14 +820,76 @@ mode as their parent."
                   (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.
@@ -625,7 +905,6 @@ interacting with Kubernetes RESOURCEs:
 - `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.
 
@@ -658,16 +937,13 @@ DOC-PREFIX is a string used to construct the docstring of the command,
 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
@@ -692,12 +968,8 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are:
   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)))
@@ -705,10 +977,8 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are:
         (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))
@@ -721,519 +991,82 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are:
        ;; 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)
@@ -1242,31 +1075,22 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
              (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
@@ -1284,84 +1108,70 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                        (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)
@@ -1376,7 +1186,6 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
          "e" #',edt-name
          "d" #',dlt-name
          "RET" #',dsp-name
-         "u" #',updt-cmd
          "E" #',expl-cmd
          ,@(mapcan
             (pcase-lambda (`(,key ,_label ,cmd))
@@ -1391,7 +1200,6 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
          "<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))
@@ -1406,20 +1214,6 @@ Optional argument DEFAULT is the minibuffer default argument." resource)
                "\\)")
        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)
@@ -1455,57 +1249,63 @@ compatibility with earlier Emacs versions."
            ("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)
@@ -1527,15 +1327,16 @@ Switch to namespace `%s' and proceed?" k8sns))
   :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))
@@ -1543,7 +1344,7 @@ Switch to namespace `%s' and proceed?" k8sns))
            (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"))))
 
@@ -1577,10 +1378,11 @@ Switch to namespace `%s' and proceed?" k8sns))
 (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: "))
@@ -1591,16 +1393,18 @@ defaulting to the current namespace."
                      (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)
@@ -1612,62 +1416,69 @@ defaulting to the current namespace."
     ((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
@@ -1675,8 +1486,8 @@ current namespace."
   :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
@@ -1684,17 +1495,19 @@ If NAMESPACE is nil or omitted, it defaults to the current namespace."
                        (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)
@@ -1723,7 +1536,7 @@ If NAMESPACE is nil or omitted, it defaults to the current namespace."
   :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,
@@ -1734,9 +1547,7 @@ optional command to run in the images."
     (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)
@@ -1747,11 +1558,13 @@ optional command to run in the images."
           (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
@@ -1759,17 +1572,20 @@ optional command to run in the images."
                    (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)
@@ -1801,13 +1617,15 @@ optional command to run in the images."
      (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)
@@ -1823,7 +1641,7 @@ optional command to run in the images."
      (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,
@@ -1835,6 +1653,7 @@ overrides the default command IMAGE runs."
           (image nil)
           (schedule nil)
           (command nil)
+          (context nil)
           (namespace nil))
       (dolist (arg (kubed-transient-args 'kubed-transient-create-cronjob))
         (cond
@@ -1844,37 +1663,39 @@ overrides the default command IMAGE runs."
           (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)
@@ -1899,7 +1720,7 @@ overrides the default command IMAGE runs."
      (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,
@@ -1909,6 +1730,7 @@ DEFAULT-BACKEND is the service to use as a backend for unhandled URLs."
    (interactive
     (let ((name (read-string "Create ingress with name: "))
           (rules nil)
+          (context nil)
           (namespace nil)
           (class nil)
           (annotations nil)
@@ -1917,6 +1739,8 @@ DEFAULT-BACKEND is the service to use as a backend for unhandled URLs."
         (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)
@@ -1926,7 +1750,7 @@ DEFAULT-BACKEND is the service to use as a backend for unhandled URLs."
          ((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
@@ -1934,14 +1758,14 @@ DEFAULT-BACKEND is the service to use as a backend for unhandled URLs."
                    (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)
@@ -1983,8 +1807,7 @@ Optional argument DEFAULT is the minibuffer default argument."
             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)
@@ -2035,17 +1858,17 @@ Optional argument DEFAULT is the minibuffer default argument."
                 (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\".
@@ -2098,8 +1921,8 @@ completion candidates."
                      '("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)
@@ -2109,13 +1932,14 @@ completion candidates."
              (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)
@@ -2127,13 +1951,14 @@ completion candidates."
     (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,
@@ -2150,8 +1975,9 @@ command line, that overrides the container command instead of just
 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)
@@ -2160,6 +1986,8 @@ providing it with arguments."
          (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))
@@ -2171,7 +1999,7 @@ providing it with arguments."
          (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
@@ -2193,6 +2021,7 @@ providing it with arguments."
                     (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"))
@@ -2203,28 +2032,31 @@ providing it with arguments."
                      (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
@@ -2232,14 +2064,14 @@ use without prompting: if the pod has a
 \"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.
@@ -2247,32 +2079,31 @@ use it; otherwise, fall back to prompting."
                       (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
@@ -2286,32 +2117,29 @@ use it; otherwise, fall back to prompting."
                     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)
@@ -2379,7 +2207,7 @@ If PREFIX nil, it defaults to the value of `transient-current-command'."
          (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.
@@ -2391,65 +2219,72 @@ prompt for CONTAINER as well; STDIN is t unless you call this command
 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.
@@ -2461,43 +2296,39 @@ prompt for CONTAINER as well; STDIN is t unless you call this command
 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"))
@@ -2512,7 +2343,7 @@ with \\[universal-argument] \\[universal-argument]; and TTY is t unless\
   (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.
@@ -2522,16 +2353,18 @@ STRATEGY is the patch type to use, one of \"json\", \"merge\" and
 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
@@ -2539,6 +2372,7 @@ Interactively, prompt for TYPE, NAME and PATCH."
                   "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))
@@ -2554,8 +2388,8 @@ Interactively, prompt for TYPE, NAME and PATCH."
 (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."
@@ -2565,40 +2399,52 @@ 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)
@@ -2612,7 +2458,7 @@ Optional argument DEFAULT is the minibuffer default argument."
            (let ((table
                   (if (zerop start)
                       ;; Complete resource type.
-                      (kubed-api-resources)
+                      (kubed-api-resources context)
                     ;; Complete (sub-)field name.
                     (with-temp-buffer
                       (call-process
@@ -2714,8 +2560,6 @@ Interactively, prompt for COMMAND with completion for `kubectl' arguments."
   "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
@@ -2743,8 +2587,6 @@ Interactively, prompt for COMMAND with completion for `kubectl' arguments."
   "<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)
index e9b095de81b18572023d151678565587f86301a1..b8f5df947349ca2fbd4de8bb854a8a557b13c4a6 100644 (file)
@@ -309,12 +309,6 @@ have a @samp{D} in the beginning of their line.
 @findex kubed-list-unmark
 @item u
 Unmark from resource at point (@code{kubed-list-unmark}).
-@kindex A
-@findex kubed-all-namespaces-mode
-@item A
-Toggle @code{kubed-all-namespaces-mode}.  When enabled, resource lists
-for namespaced resource types show resources from all namespaces, not
-just the current namespace.
 @kindex w
 @findex kubed-list-copy-as-kill
 @item w
@@ -470,8 +464,6 @@ This line defines the following commands for you:
 @item
 @code{kubed-create-serviceaccount}
 @item
-@code{kubed-update-serviceaccounts}
-@item
 @code{kubed-explain-serviceaccounts}
 @end itemize