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”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 3dGet Pod Names
Section titled “Get Pod Names”# 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
Section titled “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
Section titled “Turn that into a lisp list”Just split at every new line (\n) with split-string function
(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
Section titled “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 kubernetes or
(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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “Testing it out”Call kubernetes mode with M-x kubernetes and then look at the logs of
pod under cursor with M-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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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