Listing pods with kubectl get pods
, then select a pod name and copy paste it into kubectl logs [pod name]
- I want to streamline my workflow and stop using the terminal
- learn more about kubernetes
- main kubernetes extension for Emacs out there is greedy for permissions
- assimilate! assimilate! assimilate!
- making a major mode
- interacting with sub-processes
- providing a modern UX
- get pods from shell command with naive processing
- dump that list in an appropriate major mode
- derive our own major mode
kubectl get pods
Only keep pod name and discard first line
kubectl get pods --no-headers=true | awk '{print $1}'
Using the shell-command-to-string
function
(shell-command-to-string "kubectl get pods --no-headers=true | awk '{print $1}'")
Just split at every new line with split-string
function
(split-string (shell-command-to-string "kubectl get pods --no-headers=true | awk '{print $1}'") "\n")
There’s already a great major to display columns of data: tabulated-list-mode
.
The column format as a vector of (name width)
elements where:
name
is the column namewidth
is the column width[("Col1" 50) ("Col2" 50)]
The row entries as a list of '(id [values....])
where each element is a row where:
id
can be left nil or be a unique id for the row[values...]
is a vector of row values(list '(nil ["row1" "value1"]) '(nil ["row2" "value2"]) '(nil ["row3" "value3"]))
(let ((columns [("Col1" 50) ("Col2" 50)])
(rows (list '(nil ["row1" "value1"])
'(nil ["row2" "value2"])
'(nil ["row3" "value3"]))))
(switch-to-buffer "*temp*")
(setq tabulated-list-format columns)
(setq tabulated-list-entries rows)
(tabulated-list-init-header)
(tabulated-list-print))
Set up only one column for pod name.
(let ((columns [("Pod" 100)])
(rows (mapcar (lambda (x) `(nil [,x]))
(split-string (shell-command-to-string "kubectl get pods --no-headers=true | awk '{print $1}'") "\n"))))
(switch-to-buffer "*temp*")
(setq tabulated-list-format columns)
(setq tabulated-list-entries rows)
(tabulated-list-init-header)
(tabulated-list-print))
(define-derived-mode kubernetes-mode tabulated-list-mode "Kubernetes"
"Kubernetes mode"
(let ((columns [("Pod" 100)])
(rows (mapcar (lambda (x) `(nil [,x]))
(split-string (shell-command-to-string
"kubectl get pods --no-headers=true | awk '{print $1}'") "\n"))))
(setq tabulated-list-format columns)
(setq tabulated-list-entries rows)
(tabulated-list-init-header)
(tabulated-list-print)))
(defun kubernetes ()
(interactive)
(switch-to-buffer "*kubernetes*")
(kubernetes-mode))
Can summon with M-x kubernetes
or
(kubernetes)
- async sub-process creation -> no hanging Emacs
- redirect output to a buffer
kubectl logs redis-64896b74dc-zrw7w
Not async and meh performance
Use the call-process
function and direct it to a buffer
(let ((buffer "*kubectl-logs*"))
(call-process "kubectl" nil buffer nil "logs" "redis-64896b74dc-zrw7w")
(switch-to-buffer buffer))
Use the start-process
function instead which will create a process for you
(let ((process "*kubectl*")
(buffer "*kubectl-logs*"))
(start-process process buffer "kubectl" "logs" "-f""redis-64896b74dc-zrw7w")
(switch-to-buffer buffer))
Let’s use the optional arg
(defun kubernetes-get-logs (&optional arg)
(interactive "P")
(let ((process "*kubectl*")
(buffer "*kubectl-logs*"))
(if arg
(start-process process buffer "kubectl" "logs" "-f" "redis-64896b74dc-zrw7w")
(call-process "kubectl" nil buffer nil "logs" "redis-64896b74dc-zrw7w"))
(switch-to-buffer buffer)))
Try it with M-x kubernetes-get-logs
or C-u M-x kubernetes-get-logs
Our major mode is derived from tabulated-list-mode
so we can use the function tabulated-list-get-entry
which will give us the entry under the cursor as a vector:
(aref (tabulated-list-get-entry) 0)
Putting everything together
(defun kubernetes-get-logs (&optional arg)
(interactive "P")
(let ((process "*kubectl*")
(buffer "*kubectl-logs*")
(pod (aref (tabulated-list-get-entry) 0)))
(if arg
(start-process process buffer "kubectl" "logs" "-f" pod)
(call-process "kubectl" nil buffer nil "logs" pod))
(switch-to-buffer buffer)))
Call kubernetes mode with M-x kubernetes
and then look at the logs of pod under cursor with M-x kubernetes-get-logs
- a meaningful UI for users to interact with our major modes
- transient (from the magit project) is perfect for wrapping CLI tools
(defun test-function ()
(interactive)
(message "Test function"))
(define-transient-command test-transient ()
"Test Transient Title"
["Actions"
("a" "Action a" test-function)
("s" "Action s" test-function)
("d" "Action d" test-function)])
(test-transient)
We can easily define command line switches in our transient interface.
(defun test-function (&optional args)
(interactive
(list (transient-args 'test-transient)))
(message "args: %s" args))
(define-transient-command test-transient ()
"Test Transient Title"
["Arguments"
("-s" "Switch" "--switch")
("-a" "Another switch" "--another")]
["Actions"
("d" "Action d" test-function)])
(test-transient)
A bit more complex than simple switches, params let users enter a value.
(defun test-function (&optional args)
(interactive
(list (transient-args 'test-transient)))
(message "args %s" args))
(define-infix-argument test-transient:--message ()
:description "Message"
:class 'transient-option
:shortarg "-m"
:argument "--message=")
(define-transient-command test-transient ()
"Test Transient Title"
["Arguments"
("-s" "Switch" "--switch")
("-a" "Another switch" "--another")
(test-transient:--message)]
["Actions"
("d" "Action d" test-function)])
(test-transient)
- can just get logs
- can follow logs with
-f
- can specify tail length
--tail=100
- can combine these options
(define-infix-argument kubernetes-transient:--tail ()
:description "Tail"
:class 'transient-option
:shortarg "-t"
:argument "--tail=")
(define-transient-command kubernetes-transient ()
"Test Transient Title"
["Arguments"
("-f" "Follow" "-f")
(kubernetes-transient:--tail)]
["Actions"
("l" "Log" kubernetes-get-logs)])
(kubernetes-transient)
- read args from transient
- check if
-f
is in args to do async or not - pass the args into the process functions
(defun kubernetes-get-logs (&optional args)
(interactive
(list (transient-args 'kubernetes-transient)))
(let ((process "*kubectl*")
(buffer "*kubectl-logs*")
(pod (aref (tabulated-list-get-entry) 0)))
(if (member "-f" args)
(apply #'start-process process buffer "kubectl" "logs" pod args)
(apply #'call-process "kubectl" nil buffer nil "logs" pod args))
(switch-to-buffer buffer)))
Simply define a mode map for kubernetes-mode
(defvar kubernetes-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "l") 'kubernetes-transient)
map))
(kubernetes)
- nicer method to get pods and other columns
- error handling
- no hard coded values
- customization
- could implement a lot of kubernetes functions, not just logs
- Gist of these notes
- Full extension for kubernetes
C-h f start-process
for doc on processes- Transient Manual
- Kubernets cheatsheet