From: Eshel Yaron Date: Tue, 20 Aug 2024 08:56:06 +0000 (+0200) Subject: New transient menu 'kubed-transient-logs' X-Git-Tag: v0.4.0~12 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=52c765cb46b65f67548b3c0c826aa94d20224569;p=kubed.git New transient menu 'kubed-transient-logs' * kubed-transient.el (kubed-transient): Rearrange, delete duplicate entry, bind 'kubed-transient-logs'. (kubed-list-transient): Add TODO comment. * kubed.el (kubed-list-logs): New command. (kubed-list-create): Mark as specific to 'kubed-list-mode'. (kubed-list-mode): Tweak comment. (kubed-define-resource): Add ':logs' keyword argument for generating 'kubed-logs-for-RESOURCE' commands. (kubed-logs): Extend with more optional arguments, integrate with new transient menu. --- diff --git a/kubed-transient.el b/kubed-transient.el index cfcd059..968d63c 100644 --- a/kubed-transient.el +++ b/kubed-transient.el @@ -47,25 +47,107 @@ (transient-define-prefix kubed-transient () "Perform Kubernetes operation." ["Kubernetes" - ;; First column. [ :pad-keys t ("RET" "Display" kubed-transient-display) ("+" "Create" kubed-transient-create) ("*" "Apply" kubed-transient-apply) - ("e" "Edit" kubed-transient-edit) ("D" "Delete" kubed-transient-delete)] - ;; Second column. - [("r" "Run" kubed-transient-run) + [("e" "Edit" kubed-transient-edit) + ("r" "Run" kubed-transient-run) ("a" "Attach" kubed-transient-attach) - ("X" "Exec" kubed-transient-exec) - ("R" "Rollout" kubed-transient-rollout)] - ;; Third column. - [("d" "Diff" kubed-transient-diff) + ("X" "Exec" kubed-transient-exec)] + [("l" "Logs" kubed-transient-logs) + ("d" "Diff" kubed-transient-diff) ("P" "Patch" kubed-transient-patch) - ("R" "Rollout" kubed-transient-rollout) - ("E" "Explain" kubed-explain) + ("R" "Rollout" kubed-transient-rollout)] + [("E" "Explain" kubed-explain) ("!" "Command line" kubed-kubectl-command)]]) +(defmacro kubed-transient-logs-for-resource (resource &optional plural) + "Define transient menu for showing logs for Kuberenetes RESOURCE. + +Optional argument PLURAL is the plural form of RESOURCE. If nil, it +defaults to \"RESOURCEs\"." + (let ((name (intern (concat "kubed-transient-logs-for-" resource)))) + `(transient-define-prefix ,name (&optional value) + ,(format "Show logs for a Kubernetes %s." resource) + [,(format "Kubernetes Logs for %s\n" (capitalize resource)) + ["Actions" + ("l" "Show Logs" ,(intern (concat "kubed-logs-for-" resource))) + ("!" "Command line" kubed-kubectl-command)] + ["Options" + ("-n" "Namespace" "--namespace=" + :prompt "Namespace" :reader kubed-transient-read-namespace) + ("-C" "Context" "--context=" + :prompt "Context" :reader kubed-transient-read-context) + ("-b" "Limit bytes" "--limit-bytes=" + :prompt "Byte limit: " :reader transient-read-number-N+) + ("-t" "Limit lines" "--tail=" + :prompt "Byte limit: " :reader transient-read-number-N+) + ("-S" "Since time" "--since-time=" + :prompt "Since time: " :reader transient-read-date)] + ["Switches" + ("-A" "All containers" "--all-containers") + ("-f" "Stream logs" "--follow") + ("-P" "Add pod and container" "--prefix") + ("-T" "Add timestamps" "--timestamps")]] + (interactive + (list (kubed-transient-args 'kubed-transient-logs))) + (transient-setup ',name nil nil + :value value + :scope '("logs" ,(concat (or plural + (concat resource "s")) + "/")))))) + +;;;###autoload (autoload 'kubed-transient-logs-for-pod "kubed-transient" nil t) +(kubed-transient-logs-for-resource "pod") + +;;;###autoload (autoload 'kubed-transient-logs-for-deployment "kubed-transient" nil t) +(kubed-transient-logs-for-resource "deployment") + +;;;###autoload (autoload 'kubed-transient-logs-for-service "kubed-transient" nil t) +(kubed-transient-logs-for-resource "service") + +;;;###autoload (autoload 'kubed-transient-logs-for-job "kubed-transient" nil t) +(kubed-transient-logs-for-resource "job") + +;;;###autoload (autoload 'kubed-transient-logs-for-replicaset "kubed-transient" nil t) +(kubed-transient-logs-for-resource "replicaset") + +;;;###autoload (autoload 'kubed-transient-logs-for-statefulset "kubed-transient" nil t) +(kubed-transient-logs-for-resource "statefulset") + +;;;###autoload (autoload 'kubed-transient-logs "kubed-transient" nil t) +(transient-define-prefix kubed-transient-logs () + "Show logs for containers running in Kubernetes." + ["Kubernetes Logs\n" + ["Kinds" + ("p" "Pod" kubed-transient-logs-for-pod) + ("d" "Deployment" kubed-transient-logs-for-deployment) + ("j" "Job" kubed-transient-logs-for-job) + ("s" "Service" kubed-transient-logs-for-service) + ("l" "Any type" kubed-logs) + ("!" "Command line" kubed-kubectl-command)] + ["Options" + ("-n" "Namespace" "--namespace=" + :prompt "Namespace" :reader kubed-transient-read-namespace) + ("-C" "Context" "--context=" + :prompt "Context" :reader kubed-transient-read-context) + ("-b" "Limit bytes" "--limit-bytes=" + :prompt "Byte limit: " :reader transient-read-number-N+) + ("-t" "Limit lines" "--tail=" + :prompt "Byte limit: " :reader transient-read-number-N+) + ("-S" "Since time" "--since-time=" + :prompt "Since time: " :reader transient-read-date)] + ["Switches" + ("-A" "All containers" "--all-containers") + ("-f" "Stream logs" "--follow") + ("-P" "Add pod and container" "--prefix") + ("-T" "Add timestamps" "--timestamps")]] + (interactive) + (transient-setup 'kubed-transient-logs nil nil + :scope '("logs"))) + ;;;###autoload (autoload 'kubed-transient-rollout "kubed-transient" nil t) (transient-define-prefix kubed-transient-rollout () "Manage Kubernetes deployments." @@ -410,6 +492,7 @@ ;;;###autoload (autoload 'kubed-list-transient "kubed-transient" nil t) (transient-define-prefix kubed-list-transient () "Help for Kubernetes resource list buffers." + ;; TODO: Add a type-specific group with `:setup-children'. ["Kubernetes Resources:" ["Select" :pad-keys t diff --git a/kubed.el b/kubed.el index 30c0ec1..9a9b500 100644 --- a/kubed.el +++ b/kubed.el @@ -856,9 +856,17 @@ regardless of QUIET." 0))) (user-error "No Kubernetes resource at point"))) +(defun kubed-list-logs (click) + "Show logs for Kubernetes resource at CLICK." + (interactive (list last-nonmenu-event) kubed-list-mode) + (if-let ((resource (tabulated-list-get-id (mouse-set-point click)))) + (kubed-logs kubed-list-type resource kubed-list-context kubed-list-namespace + t t nil t) + (user-error "No Kubernetes resource at point"))) + (defun kubed-list-create (definition &optional kind) "Create Kubernetes resource of kind KIND from definition file DEFINITION." - (interactive (list (kubed-read-resource-definition-file-name))) + (interactive (list (kubed-read-resource-definition-file-name)) kubed-list-mode) (kubed-create definition kind kubed-list-context) (kubed-list-update t)) @@ -1002,7 +1010,7 @@ mode as their parent." (while (not (eobp)) (unless (eq (char-after) ?\s) (push (cons (tabulated-list-get-id) - ;; Preserve mark properties. + ;; Preserve mark text properties. (buffer-substring (point) (1+ (point)))) marks)) (forward-line))) @@ -1205,7 +1213,8 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are: add DEFINITION to `kubed-RESOURCE-menu-map' with the label LABEL. - `:plural PLURAL': specify plural form of RESOURCE, as a symbol. If you omit this keyword argument, the plural form defaults to RESOURCE - followed by \"s\"." + followed by \"s\". +- `:logs t': generate `kubed-logs-for-RESOURCE' command." (declare (indent 2)) (let ((plrl-var (intern (format "%Ss" resource))) (read-fun (intern (format "kubed-read-%S" resource))) @@ -1214,7 +1223,8 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are: (crt-name (intern (format "kubed-create-%S" resource))) (map-name (intern (format "kubed-%S-prefix-map" resource))) (menu-map (intern (format "kubed-%S-menu-map" resource))) - (namespaced t) + (logs-cmd (intern (format "kubed-logs-for-%S" resource))) + (namespaced t) (logs nil) (keyword nil) frmt-var buff-fun list-cmd expl-cmd dlt-name mod-name ctxt-fun crt-spec prf-keys hist-var) @@ -1224,6 +1234,7 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are: (setq keyword (pop commands)) (cond ((eq keyword :namespaced) (setq namespaced (pop commands))) + ((eq keyword :logs) (setq logs (pop commands))) ((eq keyword :create) (setq crt-spec (pop commands))) ((eq keyword :prefix) (setq prf-keys (pop commands))) ((eq keyword :plural) (setq plrl-var (pop commands))) @@ -1238,6 +1249,9 @@ Other keyword arguments that go between PROPERTIES and COMMANDS are: 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))) + + (when logs (push `("L" "Show Logs" ,logs-cmd) prf-keys)) + ;; Generate code. `(progn (setf (alist-get ,(symbol-name plrl-var) kubed--columns nil nil #'string=) @@ -1431,6 +1445,56 @@ a prefix argument \\[universal-argument], prompt for CONTEXT too." (kubed-create definition ,(symbol-name resource) context) (kubed-update ,(symbol-name plrl-var) context))) + ,@(when logs + `((defun ,logs-cmd + ( ,resource &optional context namespace + container follow limit prefix since tail timestamps) + ,(format "Show logs for Kuberenetes %S %s." + resource (upcase (symbol-name resource))) + (interactive + (let ( ,resource context namespace + container follow limit prefix since tail timestamps) + (dolist (arg (kubed-transient-args + ',(intern (format "kubed-transient-logs-for-%S" resource)))) + (cond + ((string-match "--namespace=\\(.+\\)" arg) + (setq namespace (match-string 1 arg))) + ((string-match "--context=\\(.+\\)" arg) + (setq context (match-string 1 arg))) + ((string-match "--limit-bytes=\\(.+\\)" arg) + (setq limit (string-to-number (match-string 1 arg)))) + ((string-match "--tail=\\(.+\\)" arg) + (setq tail (string-to-number (match-string 1 arg)))) + ((string-match "--since-time=\\(.+\\)" arg) + (setq since (match-string 1 arg))) + ((equal "--all-containers" arg) (setq container t)) + ((equal "--follow" arg) (setq follow t)) + ((equal "--prefix" arg) (setq prefix t)) + ((equal "--timestamps" arg) (setq timestamps t)))) + (unless context + (setq context + (let ((cxt (kubed-local-context))) + (if (equal current-prefix-arg '(16)) + (kubed-read-context "Context" cxt) + cxt)))) + (unless namespace + (setq namespace (kubed--namespace context current-prefix-arg))) + (setq ,resource (,read-fun "Show logs for" + (and (equal context kubed-list-context) + (equal namespace kubed-list-namespace) + (equal ,(symbol-name plrl-var) kubed-list-type) + (tabulated-list-get-id (mouse-set-point last-nonmenu-event))) + nil context namespace)) + (list ,resource context namespace + ,(if (eq resource 'pod) + '(or container + (kubed-read-container pod "Container" t + context namespace)) + 'container) + follow limit prefix since tail timestamps))) + (kubed-logs ,(symbol-name plrl-var) ,resource context namespace container + follow limit prefix since tail timestamps)))) + ,@(let ((click-var (gensym "click"))) (mapcar (pcase-lambda (`(,suffix ,_key ,desc . ,body)) @@ -1445,6 +1509,8 @@ a prefix argument \\[universal-argument], prompt for CONTEXT too." (defvar-keymap ,(intern (format "kubed-%S-mode-map" plrl-var)) :doc ,(format "Keymap for `%S" mod-name) "+" #',crt-name + ,@(when logs `("l" #'kubed-list-logs + "L" #',(intern (format "kubed-transient-logs-for-%S" resource)))) ,@(mapcan (pcase-lambda (`(,suffix ,key ,_desc . ,_body)) (when key @@ -1611,8 +1677,8 @@ Interactively, use the current context. With a prefix argument (number-to-string (1+ (seq-count (lambda (c) (= c ?,)) cs))))) :right-align t) (starttime ".status.startTime" 20)) - :prefix (("L" "Show Logs" kubed-logs) - ("A" "Attach" kubed-attach) + :logs t + :prefix (("A" "Attach" kubed-attach) ("X" "Execute" kubed-exec) ("F" "Forward Port" kubed-forward-port-to-pod)) (dired "C-d" "Start Dired in" @@ -1661,10 +1727,6 @@ Switch to namespace `%s' and proceed?" kubed-list-namespace)) (read-string "Execute command: ")))) (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 - 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 @@ -1750,7 +1812,8 @@ With a prefix argument, prompt for CONTEXT instead." (clusterip ".spec.clusterIP" 16) (externalip ".spec.externalIPs[*]" 16) (ports ".spec.ports[*].port" 6) - (creationtimestamp ".metadata.creationTimestamp" 20))) + (creationtimestamp ".metadata.creationTimestamp" 20)) + :logs t) ;;;###autoload (autoload 'kubed-display-secret "kubed" nil t) ;;;###autoload (autoload 'kubed-edit-secret "kubed" nil t) @@ -1808,6 +1871,7 @@ defaulting to the current namespace." (kubed-define-resource job ((status ".status.conditions[0].type" 10) (starttime ".status.startTime" 20)) :prefix (("c" "Create from Cron" kubed-create-job-from-cronjob)) + :logs t :create ((name image &optional context namespace command) "Create Kubernetes job with name NAME executing COMMAND in IMAGE. @@ -1973,6 +2037,7 @@ NAMESPACE too. With a double prefix argument, also prompt for CONTEXT." ( creationtimestamp ".metadata.creationTimestamp" 20)) :prefix (("R" "Restart" kubed-restart-deployment) ("W" "Watch" kubed-watch-deployment-status)) + :logs t :create ((name images &optional context namespace replicas port command) "Deploy IMAGES to Kubernetes in deployment with name NAME. @@ -2047,7 +2112,8 @@ optional command to run in the images." :right-align t) (ownerkind ".metadata.ownerReferences[0].kind" 12) (ownername ".metadata.ownerReferences[0].name" 16) - (creationtimestamp ".metadata.creationTimestamp" 20))) + (creationtimestamp ".metadata.creationTimestamp" 20)) + :logs t) ;;;###autoload (autoload 'kubed-display-statefulset "kubed" nil t) ;;;###autoload (autoload 'kubed-edit-statefulset "kubed" nil t) @@ -2062,7 +2128,8 @@ optional command to run in the images." :right-align t) (ownerkind ".metadata.ownerReferences[0].kind" 12) (ownername ".metadata.ownerReferences[0].name" 16) - (creationtimestamp ".metadata.creationTimestamp" 20))) + (creationtimestamp ".metadata.creationTimestamp" 20)) + :logs t) (defun kubed-cronjob-suspended-p (cj &optional context ns) "Return non-nil if cronjob CJ in CONTEXT and namespace NS is suspended." @@ -2336,6 +2403,8 @@ Optional argument DEFAULT is the minibuffer default argument." (defun kubed-current-namespace (&optional context) "Return current Kubernetes namespace for context CONTEXT." + ;; FIXME: If no namespace is configured as the default for CONTEXT, + ;; suggest setting one. (car (process-lines kubed-kubectl-program "config" "view" "-o" @@ -2639,29 +2708,78 @@ use it; otherwise, fall back to prompting." nil t nil nil default)))) ;;;###autoload -(defun kubed-logs (pod container context namespace) - "Show logs for container CONTAINER in Kubernetes pod POD." +(defun kubed-logs + ( type resource &optional context namespace + container follow limit prefix since tail timestamps) + "Show logs for container CONTAINER in Kubernetes RESOURCE of type TYPE. + +Optional argument CONTEXT is the `kubectl' context to use; NAMESPACE is +the namespace for RESOURCE; CONTAINER is the name of the container in +RESOURCE to show logs for, or t which specifies all relevant containers. + +With a prefix argument, prompt for NAMESPACE; with a double prefix +argument, also prompt for CONTEXT." (interactive - (let* ((context (kubed-local-context)) - (context (if (equal current-prefix-arg '(16)) - (kubed-read-context "Context" context) - context)) - (n (kubed--namespace context current-prefix-arg)) - (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 ( type resource context namespace + container follow limit prefix since tail timestamps) + (dolist (arg (kubed-transient-args 'kubed-transient-logs)) + (cond + ((string-match "--namespace=\\(.+\\)" arg) + (setq namespace (match-string 1 arg))) + ((string-match "--context=\\(.+\\)" arg) + (setq context (match-string 1 arg))) + ((string-match "--limit-bytes=\\(.+\\)" arg) + (setq limit (string-to-number (match-string 1 arg)))) + ((string-match "--tail=\\(.+\\)" arg) + (setq tail (string-to-number (match-string 1 arg)))) + ((string-match "--since-time=\\(.+\\)" arg) + (setq since (match-string 1 arg))) + ((equal "--all-containers" arg) (setq container t)) + ((equal "--follow" arg) (setq follow t)) + ((equal "--prefix" arg) (setq prefix t)) + ((equal "--timestamps" arg) (setq timestamps t)))) + (unless context + (setq context + (let ((cxt (kubed-local-context))) + (if (equal current-prefix-arg '(16)) + (kubed-read-context "Context" cxt) + cxt)))) + (setq type (kubed-read-resource-type "Type of resource to display" + nil context)) + (unless namespace + (setq namespace (kubed--namespace context current-prefix-arg))) + (setq resource (kubed-read-resource-name type "Show logs for" nil nil + context namespace)) + (list type resource context namespace + (or container + (and (string= type "pods") + (kubed-read-container resource "Container" t + context namespace))) + follow limit prefix since tail timestamps))) (let* ((context (or context (kubed-local-context))) (namespace (or namespace (kubed--namespace context))) (buf (generate-new-buffer - (format "*kubed-logs %s[%s] in %s[%s]*" - pod container namespace context)))) + (format "*kubed-logs %s/%s%s in %s[%s]*" + type resource + (cond + ((stringp container) (concat "[" container "]")) + (container "[all containers]") + (t "")) + namespace context)))) (with-current-buffer buf (run-hooks 'kubed-logs-setup-hook)) - (message "Getting logs for container `%s' in pod `%s' in namespace `%s'..." - container pod namespace) - (start-process "*kubed-logs*" buf - kubed-kubectl-program "logs" - "-f" "-c" container pod - "-n" namespace "--context" context) + (apply #'start-process "*kubed-logs*" buf + kubed-kubectl-program "logs" (concat type "/" resource) + "--context" context "-n" namespace + (append + (cond + ((stringp container) (list "-c" container)) + (container '("--all-containers"))) + (when follow '("--follow")) + (when prefix '("--prefix")) + (when timestamps '("--timestamps")) + (when since (list "--since" since)) + (when tail (list "--tail" (number-to-string tail))) + (when limit (list "--limit-bytes" (number-to-string limit))))) (display-buffer buf))) (defvar kubed-port-forward-process-alist nil