]> git.eshelyaron.com Git - kubed.git/commitdiff
New transient menu 'kubed-transient-logs'
authorEshel Yaron <me@eshelyaron.com>
Tue, 20 Aug 2024 08:56:06 +0000 (10:56 +0200)
committerEshel Yaron <me@eshelyaron.com>
Tue, 20 Aug 2024 08:56:06 +0000 (10:56 +0200)
* 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.

kubed-transient.el
kubed.el

index cfcd0592328f0a852e914f0d9735982c371f9ed6..968d63ca6100c0e7796da5a2be7fa074f198eefd 100644 (file)
 (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."
 ;;;###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
index 30c0ec1246589e7ca6e81969392abad5931ed88e..9a9b50058ab316486507cd267fc4b34f9b8ec956 100644 (file)
--- 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