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)
After some feedback, I wanted to share that it is simpler and better here to not define the infix argument separately. Instead, the transient could be defined this way and have the same effect
(define-transient-command test-transient ()
"Test Transient Title"
["Arguments"
("-s" "Switch" "--switch")
("-a" "Another switch" "--another")
("-m" "Message" "--message=")] ;; simpler
["Actions"
("d" "Action d" test-function)])
- 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
I admittedly have not yet done enough research. ( I plan too. ) But as someone coming from the outside, what's not clear to me about transient is how to handle switches which can be used multiple times ( like the --mount switch on docker, for example ). All the examples I've seen seem to assume a switch is only used once and only has a single value[1]. From my first look at the code, I presume I'll need to make a new class which inherits from transient-argument, and keeps a store of the multiple values and renders those in transient-format-value using the switch and use this object in a non-exiting infix command. I plan on trying to make this work in the next couple of days.
Does this sound off-base? Does anyone have any examples of a use case like this? There are certainly plenty of command line tools which support options being used multiple times. ( the -f option for rsync for example )
[1] The code for transient-format-value on a transient option seems to anticipate possible values, but I haven't yet tracked down how those can turn up.