Kubernetes and Emacs Package
Kubernetes and Emacs
Section titled “Kubernetes and Emacs”Source: Conquering Kubernetes with Emacs, https://github.com/abrochard/kubel
Kubernetes Logs in Emacs
Section titled “Kubernetes Logs in Emacs”A tutorial on building a major mode and user experience to interacting with Kubernetes using command in the background with a UI in Emacs.
Adrien Brochard talks about how to integrate the Kubernetes client into an Emacs workflow. The key takeaways have little to do about Kubernetes itself, but rather focus on the design and implementation of an integration between two complex systems: from how to deal with async sub-processes, defining an appropriate major mode, to proposing a modern and intelligent user experience.
Get Pods
Section titled “Get Pods”-
Basic
Terminal window kubectl get podsNAME READY STATUS RESTARTS AGEfrontend-6f567b7966-6pgzs 1/1 Running 0 3dhello-node-7f5b6bd6b8-48kk4 1/1 Running 0 3dredis-64896b74dc-zrw7w 1/1 Running 0 3d -
Get Pod Names
Terminal window # Results will only be list of pod nameskubectl get pods --no-headers=true | awk '{print $1}' -
Turn command output to string using Emacs Lisp
shell-command-to-string(shell-command-to-string "kubectl get pods --no-headers=true | awk '{print $1}'") -
Turn that into a lisp list
Just split at every new line (
\n) withsplit-stringfunction(split-string (shell-command-to-string "kubectl get pods --no-headers=true | awk '{print $1}'") "\n")("frontend-6f567b7966-6pgzs" "hello-node-7f5b6bd6b8-48kk4" "redis-64896b74dc-zrw7w" "")
Using Tabulated List Mode
Section titled “Using Tabulated List Mode”Good for working with columns of data. Documentation is low.
The column format as a vector of (name width) elements where:
nameis the column namewidthis the column width
The row entries as a list of '(id [values....]) where each element is
a row where:
idcan be left nil or be a unique id for the row[values...]is a vector of row values
;; Begin with column definition - width of 50, then 3 rows(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) ;; Print list out (tabulated-list-print))-
Dump our pod lists into tabulated-list-mode
Set up only one column for pod name. Print list to temp buffer.
(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))#+end_src**** Putting it All Together: Make a Major Mode for List- Define a new major mode based on =tabulated-list-mode=, called Kubernetes- When mode is triggered, do tabulated list from above- Add function to call it#+begin_src elisp :results output silent(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))Run with
M-x kubernetesor(kubernetes)
Get Kubernetes Logs
Section titled “Get Kubernetes Logs”- async process creation to prevent blocking Emacs
- Output goes to a buffer
-
Basic Call a process with blocking
-
Use the
call-processfunction and direct it to a buffer. Use kubectl logs and pod name. -
Problem is synchronous blocking and large logs will hang Emacs
(let ((buffer "*kubectl-logs*"))(call-process "kubectl" nil buffer nil "logs" "redis-64896b74dc-zrw7w")(switch-to-buffer buffer))
-
-
Async Process
-
Use the
start-processfunction instead which will create a process for you -
Process is continuing to running logs and has not stopped until you stop it
(let ((process "*kubectl*")(buffer "*kubectl-logs*"))(start-process process buffer "kubectl" "logs" "-f""redis-64896b74dc-zrw7w")(switch-to-buffer buffer))
-
-
Async get process logs
-
Combine above to use the optional arg
-
Issue is hardcoded pod name
(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-logsorC-u M-x kubernetes-get-logs
-
How to connect that function to our major mode
Section titled “How to connect that function to our major mode”-
Our major mode is derived from
tabulated-list-modeso we can use the functiontabulated-list-get-entrywhich will give us the entry under the cursor as a vector:(aref (tabulated-list-get-entry) 0)
-
Putting it All Together: Get logs for a specific pod where argument is pod name under the cursor
(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))) -
Testing it out
Call kubernetes mode with
M-x kubernetesand then look at the logs of pod under cursor withM-x kubernetes-get-logs
Making a Modern User Experience
Section titled “Making a Modern User Experience”- UI for users to interact with major modes
- Transient from magit project - can wrap CLI tools. Need to import it into Emacs for use.
-
A simple transient
(require 'transient)(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) -
Transient with switches
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) -
Transient with params
-
More complex than simple switches, params let users enter a value.
-
Params remember what you inputted as their 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)
-
-
Kubernetes-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)
-
-
Updating our
kubernetes-get-logs- read args from transient
- check if
-fis 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))) -
Connecting the transient to our kubernetes major mode
Define a mode map for
kubernetes-mode(defvar kubernetes-mode-map(let ((map (make-sparse-keymap)))(define-key map (kbd "l") 'kubernetes-transient)map)) -
Trying it out
(kubernetes)
Conclusion
Section titled “Conclusion”What could be improved
Section titled “What could be improved”- Get pods and other columns like status
- Error handling
- No hard coded values
- Allow customization
- Implement other kubernetes functions, not just logs
See Also
Section titled “See Also”Resources
Section titled “Resources”- Gist of these notes
- Full extension for kubernetes
C-h f start-processfor doc on processes- Transient Manual
- Kubernetes cheatsheet