From: Eshel Yaron Date: Mon, 5 Aug 2024 10:27:27 +0000 (+0200) Subject: Major cleanup: simplify and extend resource lists X-Git-Tag: v0.3.0~5 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=977ac8d324a15a72793efaf013fa5885045f1c9a;p=kubed.git Major cleanup: simplify and extend resource lists 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. --- diff --git a/kubed-transient.el b/kubed-transient.el index b3f1e3d..54b1378 100644 --- a/kubed-transient.el +++ b/kubed-transient.el @@ -196,11 +196,11 @@ ("-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"))) diff --git a/kubed.el b/kubed.el index 1b10b5a..9305d91 100644 --- 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. ;; @@ -26,11 +25,9 @@ ;; 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) ;; @@ -47,10 +44,6 @@ "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 "" #'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" . ,edt-name) "" '("Delete" . ,dlt-name) "" '("Display" . ,dsp-name) - "" '("Update" . ,updt-cmd) "" '("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 Resource" . kubed-patch) "" '("Diff Config with Live" . kubed-diff) "" '("Invoke kubectl" . kubed-kubectl-command) - "" '("Toggle Namespacing" . kubed-all-namespaces-mode) - "" '("Update Resource Lists" . kubed-update-all) "" '("Explain Type or Field" . kubed-explain) "" '("Set Current Context" . kubed-use-context) "" '("Run Image" . kubed-run) diff --git a/kubed.texi b/kubed.texi index e9b095d..b8f5df9 100644 --- a/kubed.texi +++ b/kubed.texi @@ -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