From: Andreas Gerler Date: Wed, 1 Jun 2022 13:12:26 +0000 (+0200) Subject: update kubel X-Git-Url: http://git.bundesbrandschatzamt.de/?a=commitdiff_plain;h=88045ac1e706b1d0f178d5f85de031c67ec55c93;p=emacsconfig.git update kubel Signed-off-by: Andreas Gerler --- diff --git a/elpa/kubel-20201223.1730/kubel-autoloads.el b/elpa/kubel-20201223.1730/kubel-autoloads.el deleted file mode 100644 index c04476a1..00000000 --- a/elpa/kubel-20201223.1730/kubel-autoloads.el +++ /dev/null @@ -1,29 +0,0 @@ -;;; kubel-autoloads.el --- automatically extracted autoloads -;; -;;; Code: - -(add-to-list 'load-path (directory-file-name - (or (file-name-directory #$) (car load-path)))) - - -;;;### (autoloads nil "kubel" "kubel.el" (0 0 0 0)) -;;; Generated autoloads from kubel.el - -(autoload 'kubel "kubel" "\ -Invoke the kubel buffer. - -DIRECTORY is optional for TRAMP support. - -\(fn &optional DIRECTORY)" t nil) - -(if (fboundp 'register-definition-prefixes) (register-definition-prefixes "kubel" '("kubel-"))) - -;;;*** - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; coding: utf-8 -;; End: -;;; kubel-autoloads.el ends here diff --git a/elpa/kubel-20201223.1730/kubel-pkg.el b/elpa/kubel-20201223.1730/kubel-pkg.el deleted file mode 100644 index 86000bee..00000000 --- a/elpa/kubel-20201223.1730/kubel-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;;; Generated package description from /Users/andreas/.emacs.d/elpa/kubel-20201223.1730/kubel.el -*- no-byte-compile: t -*- -(define-package "kubel" "20201223.1730" "Control Kubernetes with limited permissions" '((transient "0.1.0") (emacs "25.3") (dash "2.12.0") (s "1.2.0") (yaml-mode "0.0.14")) :commit "9b1aa4830a4470ff8f11ecc0bc80b5cdaee473fd" :authors '(("Adrien Brochard")) :maintainer '("Adrien Brochard") :keywords '("kubernetes" "k8s" "tools" "processes") :url "https://github.com/abrochard/kubel") diff --git a/elpa/kubel-20201223.1730/kubel.el b/elpa/kubel-20201223.1730/kubel.el deleted file mode 100644 index d1741d8d..00000000 --- a/elpa/kubel-20201223.1730/kubel.el +++ /dev/null @@ -1,1103 +0,0 @@ -;;; kubel.el --- Control Kubernetes with limited permissions -*- lexical-binding: t; -*- - -;; Copyright (C) 2018, Adrien Brochard - -;; This file is NOT part of Emacs. - -;; This program is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License as -;; published by the Free Software Foundation; either version 2 of the -;; License, or (at your option) any later version. - - - -;; This program is distributed in the hope that it will be useful, but -;; WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -;; General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program; if not, write to the Free Software -;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 -;; USA - -;; Version: 1.0 -;; Package-Version: 20201223.1730 -;; Package-Commit: 9b1aa4830a4470ff8f11ecc0bc80b5cdaee473fd -;; Author: Adrien Brochard -;; Keywords: kubernetes k8s tools processes -;; URL: https://github.com/abrochard/kubel -;; License: GNU General Public License >= 3 -;; Package-Requires: ((transient "0.1.0") (emacs "25.3") (dash "2.12.0") (s "1.2.0") (yaml-mode "0.0.14")) - -;;; Commentary: - -;; Emacs extension for controlling Kubernetes with limited permissions. - -;;; Usage: - -;; To list the pods in your current context and namespace, call -;; -;; M-x kubel -;; -;; To set the path to the kubectl config file, call: -;; M-x kubel-set-kubectl-config-file -;; -;; or -;; -;; (kubel-set-kubectl-config-file ) -;; ex: (kubel-set-kubectl-config-file "~/.kube/another-config") -;; -;; To set said namespace and context, respectively call -;; -;; M-x kubel-set-namespace -;; M-x kubel-set-context -;; -;; Note that namespace will autocomplete but not context, -;; this is because I interact with kubernetes through a user who -;; does not have permissions to list namespaces. -;; -;; To switch to showing a different resource, use the `R` command or -;; -;; M-x kubel-set-resource -;; -;; This will let you select a resource and re-display the kubel buffer. - -;;; Shortcuts: - -;; On the kubel screen, place your cursor on a resource -;; -;; enter => get resource details -;; h => help popup -;; ? => help popup -;; E => quick edit any resource -;; g => refresh -;; k => delete popup -;; r => see the rollout history for resource -;; p => port forward pod -;; l => log popup -;; e => exec popup -;; j => jab deployment to force rolling update -;; C => set context -;; n => set namespace -;; R => set resource -;; K => set kubectl config file -;; F => set output format -;; f => set a substring filter for resource name -;; M-n => jump to the next highlighted resource -;; M-p => jump to previous highlighted resource -;; m => mark item -;; u => unmark item -;; M => mark all items -;; U => unmark all items -;; c => copy popup -;; $ => show process buffer - -;;; Customize: - -;; By default, kubel log tails from the last 100 lines, you can change the `kubel-log-tail-n` variable to set another line number. - -;;; Code: - -(require 'transient) -(require 'dash) -(require 's) -(require 'yaml-mode) -(require 'tramp) -(require 'subr-x) -(require 'eshell) -(require 'dired) - -(defgroup kubel nil "Customisation group for kubel." - :group 'extensions) - -(defvar kubel--list-format - [("Name" 50 t) - ("Ready" 10 t) - ("Status" 20 t) - ("Restarts" 10 t) - ("Age" 15 t)] - "List format.") - -(defconst kubel--list-sort-key - '("NAME" . nil) - "Sort table on this key.") - -(defconst kubel--status-colors - '(("Running" . "green") - ("Healthy" . "green") - ("Active" . "green") - ("Ready" . "green") - ("True" . "green") - ("Unknown" . "orange") - ("Error" . "red") - ("Evicted" . "red") - ("MemoryPressure" . "red") - ("PIDPressure" . "red") - ("DiskPressure" . "red") - ("RevisionMissing" . "red") - ("RevisionFailed" . "red") - ("NetworkUnavailable" . "red") - ("Completed" . "yellow") - ("CrashLoopBackOff" . "red") - ("Terminating" . "blue")) - "Associative list of status to color.") - -(defconst kubel--process-buffer "*kubel-process*" - "Kubel process buffer name.") - -(defcustom kubel-output "yaml" - "Format for output: json|yaml|wide|custom-columns=..." - :type 'string - :group 'kubel) - -(defcustom kubel-log-tail-n 100 - "Default number of lines to tail." - :type 'integer - :group 'kubel) - -(defcustom kubel-use-namespace-list 'auto - "Control behavior for namespace completion. - -auto - default, use `kubectl auth can-i list namespace` to determine if we can list namespaces -on - always assume we can list namespaces -off - always assume we cannot list namespaces" - :type 'symbol - :group 'kubel - :options '('auto 'on 'off)) - -(defun kubel--append-to-process-buffer (str) - "Append string STR to the process buffer." - (with-current-buffer (get-buffer-create kubel--process-buffer) - (read-only-mode -1) - (goto-char (point-max)) - (insert (format "%s\n" str)))) - -(defvar kubel--last-command nil) - -(defun kubel--log-command (process-name cmd) - "Log the kubectl command to the process buffer. - -PROCESS-NAME is the name of the process. -CMD is the kubectl command as a list." - (let ((str-cmd (if (equal 'string (type-of cmd)) cmd (mapconcat #'identity cmd " ")))) - (setq kubel--last-command str-cmd) - (kubel--append-to-process-buffer - (format "[%s]\ncommand: %s" process-name str-cmd)))) - -(defun kubel--exec-to-string (cmd) - "Replace \"shell-command-to-string\" to log to process buffer. - -CMD is the command string to run." - (kubel--log-command "kubectl-command" cmd) - (shell-command-to-string cmd)) - -(defvar kubel-namespace "default" - "Current namespace.") - -(defvar kubel-resource "Pods" - "Current resource.") - -(defvar kubel-context - (replace-regexp-in-string - "\n" "" (kubel--exec-to-string "kubectl config current-context")) - "Current context. Tries to smart default.") - -(defvar kubel-resource-filter "" - "Substring filter for resource name.") - -(defvar kubel--line-number nil - "Store the current line number to jump back after a refresh.") - -(defvar kubel-namespace-history '() - "List of previously used namespaces.") - -;; fallback list of resources if the version of kubectl doesn't support api-resources command -(defvar kubel-kubernetes-resources-list - '("Pods" - "Services" - "Namespaces" - "Nodes" - "Configmaps" - "Secrets" - "Bindings" - "PersistentVolumeClaims" - "PersistentVolumes" - "ReplicationControllers" - "ResourceQuotas" - "ServiceAccounts" - "Deployments" - "DaemonSets" - "ReplicaSets" - "StatefulSets" - "Jobs" - "hpa" - "Images" - "Ingresses" - "ClusterRoles" - "RoleBindings" - "Roles")) - -(defvar kubel--kubernetes-version-cached nil) - -(defvar kubel--kubernetes-resources-list-cached nil) - -(defvar kubel--can-get-namespace-cached nil) - -(defvar kubel--namespace-list-cached nil) - -(defvar kubel--selected-items '()) - -(defun kubel--invalidate-context-caches () - "Invalidate the context caches." - (setq kubel--kubernetes-resources-list-cached nil) - (setq kubel--kubernetes-version-cached nil) - (setq kubel--can-get-namespace-cached nil) - (setq kubel--namespace-list-cached nil)) - -(defun kubel-kubernetes-version () - "Return a list with (major-version minor-version patch)." - (let ((version-string (if (null kubel--kubernetes-version-cached) - (setq kubel--kubernetes-version-cached - (kubel--exec-to-string "kubectl version")) - kubel--kubernetes-version-cached))) - (string-match "GitVersion:\"v\\([0-9]*\\)\.\\([0-9]*\\)\.\\([0-9]*\\)[^0-9].*\"" version-string) - (list - (string-to-number (match-string 1 version-string)) - (string-to-number (match-string 2 version-string)) - (string-to-number (match-string 3 version-string))))) - -(defun kubel-kubernetes-compatible-p (version) - "Return TRUE if kubernetes version is greater than or equal to VERSION. -VERSION should be a list of (major-version minor-version patch)." - (let* - ((kubernetes-version (kubel-kubernetes-version)) - (kubernetes-major-version (nth 0 kubernetes-version)) - (kubernetes-minor-version (nth 1 kubernetes-version)) - (kubernetes-patch-version (nth 2 kubernetes-version))) - (and - (<= (nth 0 version) kubernetes-major-version) - (or (<= (nth 1 version) kubernetes-minor-version) (< (nth 0 version) kubernetes-major-version)) - (or (<= (nth 2 version) kubernetes-patch-version) (< (nth 1 version) kubernetes-minor-version))))) - -(defun kubel--populate-list () - "Return a list with a tabulated list format and \"tabulated-list-entries\"." - (let* ((body (kubel--exec-to-string (concat (kubel--get-command-prefix) " get " kubel-resource))) - (entrylist (kubel--parse-body body))) - (when (string-prefix-p "No resources found" body) - (message "No resources found")) ;; TODO exception here - (list (kubel--get-list-format entrylist) (kubel--get-list-entries entrylist)))) - -(defun kubel--column-entry (entrylist) - "Return a function of colnum to retrieve an entry in a given column for ENTRYLIST." - (function - (lambda (colnum) - (list (kubel--column-header entrylist colnum) (+ 4 (kubel--column-width entrylist colnum)) t)))) - - -(defun kubel--get-list-format (entrylist) - "Get the list format. - -ENTRYLIST is the output of the parsed body." - (defun kubel--get-column-entry (colnum) - (let ((kubel--get-entry (kubel--column-entry entrylist))) - (funcall kubel--get-entry colnum))) - (cl-map 'vector #'kubel--get-column-entry (number-sequence 0 (- (kubel--ncols entrylist) 1)))) - -(defun kubel--update-selected-items (entries) - "Check that all selected items still exist. - -ENTRIES are all resources." - (dolist (i (-difference kubel--selected-items (mapcar #'car entries))) - (setq kubel--selected-items (delete i kubel--selected-items)))) - -(defun kubel--get-list-entries (entrylist) - "Get the entries. - -ENTRYLIST is the output of the parsed body." - (let ((entries (cdr entrylist))) - (kubel--update-selected-items entries) - (mapcar (lambda (x) - (list (car x) - (vconcat [] (mapcar #'kubel--propertize-status x)))) - entries))) - -(defun kubel--parse-body (body) - "Parse the body of kubectl get resource call into a list. - -BODY is the raw output of kubectl get resource." - (let* ((lines (nbutlast (split-string body "\n"))) - (header (car lines)) - (cols (split-string header)) - (start-pos (mapcar (lambda (x) (string-match x header)) cols)) - (end-pos (delete 0 (append start-pos '("end")))) - (position (-zip-with 'cons start-pos end-pos)) - (parse-line (lambda (line) - (mapcar (lambda (pos) - (kubel--extract-value line (car pos) (cdr pos))) - position)))) - (mapcar parse-line lines))) - -(defun kubel--extract-value (line min max) - "Extract value from LINE between MIN and MAX. -If it's just white space, return -, else trim space. -If MAX is the end of the line, dynamically adjust." - (let* ((maxx (if (equal max "end") (length line) max)) - (str (substring-no-properties line min maxx))) - (if (string-match "^ +$" str) - "-" - (string-trim str)))) - -(defun kubel--ncols (entrylist) - "Return the number of columns in ENTRYLIST." - (length (car entrylist))) - -(defun kubel--nrows (entrylist) - "Return the nubmer of rows in ENTRYLIST." - (length entrylist)) - -(defun kubel--column-header (entrylist colnum) - "Return the header for a specific COLNUM in ENTRYLIST." - (nth colnum (car entrylist))) - -(defun kubel--column-width (entrylist colnum) - "Return the width of a specific COLNUM in ENTRYLIST." - (seq-max (mapcar (lambda (x) (length (nth colnum x) )) entrylist))) - -(defun kubel--buffer-name () - "Return kubel buffer name." - (format "*kubel (%s) [%s]: %s*" kubel-namespace kubel-context kubel-resource)) - -(defun kubel--items-selected-p () - "Return non-nil if there are items selected." - (>= (length kubel--selected-items) 1)) - -(defun kubel--propertize-status (status) - "Return the status in proper font color. - -STATUS is the pod status string." - (let ((pair (cdr (assoc status kubel--status-colors))) - (match (or (equal kubel-resource-filter "") (string-match-p kubel-resource-filter status))) - (selected (and (kubel--items-selected-p) (-contains? kubel--selected-items status)))) - (cond (pair (propertize status 'font-lock-face `(:foreground ,pair))) - (selected (propertize (concat "*" status) 'face 'dired-marked)) - ((not match) (propertize status 'font-lock-face '(:foreground "darkgrey"))) - (t status)))) - -(defun kubel--pop-to-buffer (name) - "Utility function to pop to buffer or create it. - -NAME is the buffer name." - (unless (get-buffer name) - (get-buffer-create name)) - (pop-to-buffer-same-window name)) - -(defun kubel--process-error-buffer (process-name) - "Return the error buffer name for the PROCESS-NAME." - (format "*%s:err*" process-name)) - -(defun kubel--sentinel (process _) - "Sentinel function for PROCESS." - (let ((process-name (process-name process)) - (exit-status (process-exit-status process))) - (kubel--append-to-process-buffer (format "[%s]\nexit-code: %s" process-name exit-status)) - (unless (eq 0 exit-status) - (let ((err (with-current-buffer (kubel--process-error-buffer process-name) - (buffer-string)))) - (kubel--append-to-process-buffer (format "error: %s" err)) - (error (format "Kubel process %s error: %s" process-name err)))))) - -(defun kubel--exec (process-name args &optional readonly) - "Utility function to run commands in the proper context and namespace. - -PROCESS-NAME is an identifier for the process. Default to \"kubel-command\". -ARGS is a ist of arguments. -READONLY If true buffer will be in readonly mode(view-mode)." - (when (equal process-name "") - (setq process-name "kubel-command")) - (let ((buffer-name (format "*%s*" process-name)) - (error-buffer (kubel--process-error-buffer process-name)) - (cmd (append (list "kubectl") (kubel--get-context-namespace) args))) - (when (get-buffer buffer-name) - (kill-buffer buffer-name)) - (when (get-buffer error-buffer) - (kill-buffer error-buffer)) - (kubel--log-command process-name cmd) - (make-process :name process-name - :buffer buffer-name - :sentinel #'kubel--sentinel - :file-handler t - :stderr error-buffer - :command cmd) - (pop-to-buffer buffer-name) - (if readonly - (with-current-buffer buffer-name - (view-mode))))) - -(defun kubel--get-resource-under-cursor () - "Utility function to get the name of the resource under the cursor. -Strip the `*` prefix if the resource is selected" - (replace-regexp-in-string - "^\*" "" (aref (tabulated-list-get-entry) 0))) - -(defun kubel--get-context-namespace () - "Utility function to return the proper context and namespace arguments." - (append - (unless (equal kubel-context "") - (list "--context" kubel-context)) - (unless (equal kubel-namespace "default") - (list "-n" kubel-namespace)))) - -(defun kubel--get-command-prefix () - "Utility function to prefix the kubectl command with proper context and namespace." - (mapconcat 'identity (append '("kubectl") (kubel--get-context-namespace)) " ")) - -(defun kubel--get-containers (pod-name &optional type) - "List the containers in a pod. - -POD-NAME is the name of the pod. -TYPE is containers or initContainers." - (unless type (setq type "containers")) - (split-string - (kubel--exec-to-string - (format "%s get pod %s -o jsonpath='{.spec.%s[*].name}'" (kubel--get-command-prefix) pod-name type)) " ")) - -(defun kubel--get-pod-labels () - "List labels of pods in a current namespace." - (delete-dups - (split-string - (replace-regexp-in-string - (regexp-quote ":") "=" - (replace-regexp-in-string - "map\\[\\(.+?\\)\\]" "\\1" - (kubel--exec-to-string - (format "%s get pod -o jsonpath='{.items[*].metadata.labels}'" (kubel--get-command-prefix)))))))) - -(defun kubel--select-resource (name) - "Prompt user to select an instance out of a list of resources. - -NAME is the string name of the resource." - (let ((cmd (format "%s get %s -o=jsonpath='{.items[*].metadata.name}'" - (kubel--get-command-prefix) name))) - (completing-read (concat (s-upper-camel-case name) ": ") - (split-string (kubel--exec-to-string cmd) " ")))) - -(defun kubel--describe-resource (name &optional describe) - "Describe a specific resource. - -NAME is the string name of the resource to decribe. -DESCRIBE is boolean to describe instead of get resource details" - (let* ((resource (kubel--select-resource name)) - (process-name (format "kubel - %s - %s" name resource))) - (if describe - (kubel--exec process-name (list "describe" name resource)) - (kubel--exec process-name (list "get" name "-o" kubel-output resource))) - (when (string-equal kubel-output "yaml") - (yaml-mode) - (kubel-yaml-editing-mode)) - (goto-char (point-min)))) - -(defun kubel--show-rollout-revision (type name) - "Show a specific revision of a certain resource. - -TYPE is the resource type. -NAME is the resource name." - (let* ((typename (format "%s/%s" type name)) - (revision (car (split-string (kubel--select-rollout typename)))) - (process-name (format "kubel - rollout - %s - %s" typename revision))) - (kubel--exec process-name - (list "rollout" "history" typename (format "--revision=%s" revision))) - (goto-char (point-min)))) - -(defun kubel--list-rollout (typename) - "Return a list of revisions with format '%number %cause'. - -TYPENAME is the resource type/name." - (let ((cmd (format "%s rollout history %s" (kubel--get-command-prefix) typename))) - (nthcdr 2 (split-string (kubel--exec-to-string cmd) "\n")))) - -(defun kubel--select-rollout (typename) - "Select a rollout version. - -TYPENAME is the resource type/name." - (let ((prompt (format "Select a rollout of %s: " typename)) - (rollouts (kubel--list-rollout typename))) - (completing-read prompt rollouts))) - -(defun kubel--is-pod-view () - "Return non-nil if this is the pod view." - (equal (capitalize kubel-resource) "Pods")) - -(defun kubel--is-deployment-view () - "Return non-nil if this is the pod view." - (-contains? '("Deployments" "deployments" "deployments.apps") kubel-resource)) - -(defun kubel--save-line () - "Save the current line number if the view is unchanged." - (if (equal (buffer-name (current-buffer)) - (kubel--buffer-name)) - (setq kubel--line-number (+ 1 (count-lines 1 (point)))) - (setq kubel--line-number nil))) - -(defun kubel--jump-back-to-line () - "Jump back to the last cached line number." - (when kubel--line-number - (goto-line kubel--line-number))) - -;; interactive -(define-minor-mode kubel-yaml-editing-mode - "Kubel Yaml editing mode. -Use C-c C-c to kubectl apply the current yaml buffer." - :init-value nil - :keymap (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-c") 'kubel-apply) - map)) - -(defun kubel-apply () - "Save the current buffer to a temp file and try to kubectl apply it." - (interactive) - (setq dir-prefix (or - (when (tramp-tramp-file-p default-directory) - (with-parsed-tramp-file-name default-directory nil - (format "/%s%s:%s@%s:" (or hop "") method user host))) - "")) - - (let* ((filename-without-tramp-prefix (format "/tmp/kubel/%s-%s.yaml" - (replace-regexp-in-string "\*\\| " "" (buffer-name)) - (floor (float-time)))) - (filename (format "%s%s" dir-prefix filename-without-tramp-prefix))) - (when (y-or-n-p "Apply the changes? ") - (unless (file-exists-p (format "%s/tmp/kubel" dir-prefix)) - (make-directory (format "%s/tmp/kubel" dir-prefix) t)) - (write-region (point-min) (point-max) filename) - (kubel--exec (format "kubectl - apply - %s" filename) (list "apply" "-f" filename-without-tramp-prefix)) - (message "Applied %s" filename)))) - -(defun kubel-get-resource-details (&optional describe) - "Get the details of the resource under the cursor. - - DESCRIBE is the optional param to describe instead of get." - (interactive "P") - (let* ((resource (kubel--get-resource-under-cursor)) - (process-name (format "kubel - %s - %s" kubel-resource resource))) - (if describe - (kubel--exec process-name (list "describe" kubel-resource (kubel--get-resource-under-cursor))) - (kubel--exec process-name (list "get" kubel-resource (kubel--get-resource-under-cursor) "-o" kubel-output))) - (when (or (string-equal kubel-output "yaml") (transient-args 'kubel-describe-popup)) - (yaml-mode) - (kubel-yaml-editing-mode)) - (goto-char (point-min)))) - - -(defun kubel--default-tail-arg (args) - "Ugly function to make sure that there is at least the default tail. - -ARGS is the arg list from transient." - (if (car (remove nil (mapcar (lambda (x) - (string-prefix-p "--tail=" x)) args))) - args - (append args (list (concat "--tail=" (format "%s" kubel-log-tail-n)))))) - -(defun kubel-get-pod-logs (&optional args type) - "Get the last N logs of the pod under the cursor. - -ARGS is the arguments list from transient. -TYPE is containers or initContainers." - (interactive - (list (transient-args 'kubel-log-popup))) - (dolist (pod (if (kubel--is-pod-view) - (if (kubel--items-selected-p) - kubel--selected-items - (list (kubel--get-resource-under-cursor))) - (list (kubel--select-resource "Pods")))) - (let* ((type (or type "containers")) - (containers (kubel--get-containers pod type)) - (container (if (equal (length containers) 1) - (car containers) - (completing-read "Select container: " containers))) - (process-name (format "kubel - logs - %s - %s" pod container))) - (kubel--exec process-name - (append '("logs") (kubel--default-tail-arg args) (list pod container)) t)))) - -(defun kubel-get-pod-logs--initContainer (&optional args) - "Get the last N logs of the pod under the cursor. - -ARGS is the arguments list from transient." - (interactive - (list (transient-args 'kubel-log-popup))) - (kubel-get-pod-logs args "initContainers")) - -(defun kubel-get-logs-by-labels (&optional args) - "Get the last N logs of the pods by labels. -ARGS is the arguments list from transient." - (interactive - (list (transient-args 'kubel-log-popup))) - (let* ((labels (kubel--get-pod-labels)) - (label (completing-read "Select container: " labels)) - (process-name (format "kubel - logs - %s" label))) - (kubel--exec process-name - (append '("logs") (kubel--default-tail-arg args) '("-l") (list label)) t))) - -(defun kubel-copy-resource-name () - "Copy the name of the pod under the cursor." - (interactive) - (kill-new (kubel--get-resource-under-cursor)) - (message "Resource name copied to kill-ring")) - -(defun kubel-copy-log-command () - "Copy the streaming log command of the pod under the cursor." - (interactive) - (kill-new - (format "%s logs -f --tail=%s %s" - (kubel--get-command-prefix) - kubel-log-tail-n - (if (kubel--is-pod-view) - (kubel--get-resource-under-cursor) - (kubel--select-resource "Pods")))) - (message "Log command copied to kill-ring")) - -(defun kubel-copy-command-prefix () - "Copy the kubectl command prefix." - (interactive) - (kill-new (kubel--get-command-prefix)) - (message "Command prefix copied to kill-ring")) - -(defun kubel-copy-last-command () - "Copy the last kubectl command ran." - (interactive) - (kill-new kubel--last-command) - (message (concat "Last command copied: " kubel--last-command))) - -(defun kubel-set-kubectl-config-file (configfile) - "Set the path to the kubectl CONFIGFILE." - (interactive "f") - (let ((configfile (or configfile "~/.kube/config"))) - (if (file-exists-p (expand-file-name configfile)) - (setenv "KUBECONFIG" (expand-file-name configfile)) - (error "Kubectl config file '%s' does not exist!" configfile)))) - -(defun kubel--can-get-namespace () - "Determine if permissions allow for `kubectl get namespace` in current context." - (cond ((eq kubel-use-namespace-list 'on) t) - ((eq kubel-use-namespace-list 'auto) - (progn - (unless kubel--can-get-namespace-cached - (setq kubel--can-get-namespace-cached - (equal "yes\n" - (kubel--exec-to-string - (format "kubectl --context %s auth can-i list namespaces" kubel-context)))))) - kubel--can-get-namespace-cached))) - -(defun kubel--get-namespace () - "Get namespaces for current context, try to recover from cache first." - (unless kubel--namespace-list-cached - (setq kubel--namespace-list-cached - (split-string (kubel--exec-to-string - (format "kubectl --context %s get namespace -o jsonpath='{.items[*].metadata.name}'" kubel-context)) " "))) - kubel--namespace-list-cached) - -(defun kubel--list-namespace () - "List namespace, either from history, or dynamically if possible." - (if (kubel--can-get-namespace) - (kubel--get-namespace) - kubel-namespace-history)) - -(defun kubel--add-namespace-to-history (namespace) - "Add NAMESPACE to history if it isn't there already." - (unless (member namespace kubel-namespace-history) - (push namespace kubel-namespace-history))) - -(defun kubel-set-namespace () - "Set the namespace." - (interactive) - (let* ((namespace (completing-read "Namespace: " (kubel--list-namespace) - nil nil nil nil "default")) - (kubel--buffer (get-buffer (kubel--buffer-name))) - (last-default-directory (when kubel--buffer - (with-current-buffer kubel--buffer default-directory)))) - (when kubel--buffer (kill-buffer kubel--buffer)) - (setq kubel-namespace namespace) - (kubel--add-namespace-to-history namespace) - (kubel last-default-directory))) - -(defun kubel-set-context () - "Set the context." - (interactive) - (let* ((kubel--buffer (get-buffer (kubel--buffer-name))) - (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) - (when kubel--buffer (kill-buffer kubel--buffer));; kill buffer for previous context if possible - (setq kubel-context - (completing-read - "Select context: " - (split-string (kubel--exec-to-string "kubectl config view -o jsonpath='{.contexts[*].name}'") " "))) - (kubel--invalidate-context-caches) - (setq kubel-namespace "default") - (kubel last-default-directory))) - -(defun kubel--fetch-api-resource-list () - "Fetch the API resource list." - (split-string (kubel--exec-to-string - ( format "kubectl --context %s api-resources -o name --no-headers=true" kubel-context )) "\n")) - -(defun kubel-set-resource (&optional refresh) - "Set the resource. -If called with a prefix argument REFRESH, refreshes -the context caches, including the cached resource list." - (interactive "P") - (when refresh (kubel--invalidate-context-caches)) - (let* ((current-buffer-name (kubel--buffer-name)) - (resource-list (if (kubel-kubernetes-compatible-p '(1 13 3)) - (if (null kubel--kubernetes-resources-list-cached) - (setq kubel--kubernetes-resources-list-cached - (kubel--fetch-api-resource-list)) - kubel--kubernetes-resources-list-cached) - kubel-kubernetes-resources-list)) - (kubel--buffer (get-buffer current-buffer-name)) - (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) - (setq kubel-resource - (completing-read "Select resource: " resource-list)) - (when kubel--buffer (kill-buffer kubel--buffer)) ;; kill buffer for previous context if possible - (kubel last-default-directory))) - -(defun kubel-set-output-format () - "Set output format of kubectl." - (interactive) - (setq kubel-output - (completing-read - "Set output format: " - '("yaml" "json" "wide" "custom-columns="))) - (kubel)) - -(defun kubel-port-forward-pod (p) - "Port forward a pod to your local machine. - -P can be a single number or a localhost:container port pair." - (interactive "sPort: ") - (let* ((port (if (string-match-p ":" p) p (format "%s:%s" p p))) - (pod (if (kubel--is-pod-view) - (kubel--get-resource-under-cursor) - (kubel--select-resource "Pods"))) - (process-name (format "kubel - port-forward - %s:%s" pod port))) - (kubel--exec process-name (list "port-forward" pod port)))) - -(defun kubel-setup-tramp () - "Setup a kubectl TRAMP." - (setq tramp-methods (delete (assoc "kubectl" tramp-methods) tramp-methods)) ;; cleanup previous tramp method - ;; TODO error message if resource is not pod - (add-to-list 'tramp-methods - `("kubectl" - (tramp-login-program "kubectl") - (tramp-login-args (,(kubel--get-context-namespace) ("exec" "-it") ("-u" "%u") ("%h") ("sh"))) - (tramp-remote-shell "sh") - (tramp-remote-shell-args ("-i" "-c"))))) ;; add the current context/namespace to tramp methods - -(defun kubel-exec-pod () - "Exec into the pod under the cursor -> `find-file." - (interactive) - (kubel-setup-tramp) - (setq dir-prefix (or - (when (tramp-tramp-file-p default-directory) - (with-parsed-tramp-file-name default-directory nil - (format "%s%s:%s@%s|" (or hop "") method user host))) - "")) - (find-file (format "/%skubectl:%s:/" dir-prefix (if (kubel--is-pod-view) - (kubel--get-resource-under-cursor) - (kubel--select-resource "Pods"))))) - -(defun kubel-exec-shell-pod () - "Exec into the pod under the cursor -> shell." - (interactive) - (kubel-setup-tramp) - (let* ((dir-prefix (or - (when (tramp-tramp-file-p default-directory) - (with-parsed-tramp-file-name default-directory nil - (format "%s%s:%s@%s|" (or hop "") method user host))) "")) - (pod (if (kubel--is-pod-view) - (kubel--get-resource-under-cursor) - (kubel--select-resource "Pods"))) - (default-directory (format "/%skubectl:%s:/" dir-prefix pod))) - (shell (format "*kubel - shell - %s*" pod)))) - -(defun kubel-exec-eshell-pod () - "Exec into the pod under the cursor -> eshell." - (interactive) - (kubel-setup-tramp) - (let* ((dir-prefix (or - (when (tramp-tramp-file-p default-directory) - (with-parsed-tramp-file-name default-directory nil - (format "%s%s:%s@%s|" (or hop "") method user host))) "")) - (pod (if (kubel--is-pod-view) - (kubel--get-resource-under-cursor) - (kubel--select-resource "Pods"))) - (default-directory (format "/%skubectl:%s:/" dir-prefix pod)) - (eshell-buffer-name (format "*kubel - eshell - %s*" pod))) - (eshell))) - -(defun kubel-delete-resource () - "Kubectl delete resource under cursor." - (interactive) - (dolist (pod (if (kubel--items-selected-p) - kubel--selected-items - (list (kubel--get-resource-under-cursor)))) - (let* ((process-name (format "kubel - delete %s - %s" kubel-resource pod)) - (args (list "delete" kubel-resource pod))) - (when (transient-args 'kubel-delete-popup) - (setq args (append args (list "--force" "--grace-period=0")))) - (kubel--exec process-name args)))) - -(defun kubel-jab-deployment () - "Make a trivial patch to force a new deployment. - -See https://github.com/kubernetes/kubernetes/issues/27081" - (interactive) - (dolist (deployment (if (kubel--is-deployment-view) - (if (kubel--items-selected-p) - kubel--selected-items - (list (kubel--get-resource-under-cursor))) - (list (kubel--select-resource "Deployments")))) - (let ((process-name (format "kubel - bouncing - %s" deployment))) - (kubel--exec process-name (list "patch" "deployment" deployment "-p" - (format "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"%s\"}}}}}" - (round (time-to-seconds)))))))) - -(defun kubel-set-filter (filter) - "Set the pod filter. - -FILTER is the filter string." - (interactive "MFilter: ") - (setq kubel-resource-filter filter) - (kubel)) - -(defun kubel--jump-to-highlight (init search reset) - "Base function to jump to highlight. - -INIT is to be called before searching. -SEARCH is to apply the search and can be repeated safely. -RESET is to be called if the search is nil after the first attempt." - (unless (equal kubel-resource-filter "") - (funcall init) - (unless (funcall search) - (funcall reset) - (funcall search)) - (beginning-of-line))) - -(defun kubel-jump-to-next-highlight () - "Jump to the next hightlighted resrouce." - (interactive) - (kubel--jump-to-highlight - #'end-of-line - (lambda () (re-search-forward kubel-resource-filter (point-max) t)) - #'beginning-of-buffer)) - -(defun kubel-jump-to-previous-highlight () - "Jump to the previou highlighted resrouce." - (interactive) - (kubel--jump-to-highlight - #'beginning-of-line - (lambda () (re-search-backward kubel-resource-filter (point-min) t)) - #'end-of-buffer)) - -(defun kubel-rollout-history () - "See rollout history for resource under cursor." - (interactive) - (kubel--show-rollout-revision kubel-resource (kubel--get-resource-under-cursor))) - -(defun kubel-changelog () - "Opens up the changelog." - (interactive) - (browse-url "https://github.com/abrochard/kubel/blob/master/CHANGELOG.md")) - -(defun kubel-quick-edit () - "Quickly edit any resource." - (interactive) - (kubel--describe-resource - (completing-read "Select resource: " kubel-kubernetes-resources-list))) - -(defun kubel-show-process-buffer () - "Show the kubel-process-buffer." - (interactive) - (pop-to-buffer kubel--process-buffer) - (special-mode)) - -(defun kubel-mark-item () - "Mark or unmark the item under cursor." - (interactive) - (let ((item (kubel--get-resource-under-cursor))) - (unless (-contains? kubel--selected-items item) - (progn - (push item kubel--selected-items) - (forward-line 1) - (kubel))))) - -(defun kubel-unmark-item () - "Unmark the item under cursor." - (interactive) - (let ((item (kubel--get-resource-under-cursor))) - (when (-contains? kubel--selected-items item) - (progn - (setq kubel--selected-items (delete item kubel--selected-items)) - (kubel))))) - -(defun kubel-mark-all () - "Mark all items." - (interactive) - (setq kubel--selected-items '()) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (push (kubel--get-resource-under-cursor) kubel--selected-items) - (forward-line 1))) - (kubel)) - -(defun kubel-unmark-all () - "Unmark all items." - (interactive) - (setq kubel--selected-items '()) - (kubel)) - -;; popups - -(define-transient-command kubel-exec-popup () - "Kubel Exec Menu" - ["Actions" - ("d" "Dired" kubel-exec-pod) - ("e" "Eshell" kubel-exec-eshell-pod) - ("s" "Shell" kubel-exec-shell-pod)]) - -(define-transient-command kubel-log-popup () - "Kubel Log Menu" - ["Arguments" - ("-f" "Follow" "-f") - ("-p" "Previous" "-p") - ("-n" "Tail" "--tail=")] - ["Actions" - ("l" "Tail pod logs" kubel-get-pod-logs) - ("i" "Tail initContainer logs" kubel-get-pod-logs--initContainer) - ("L" "Tail by labels" kubel-get-logs-by-labels)]) - -(define-transient-command kubel-copy-popup () - "Kubel Copy Menu" - ["Actions" - ("c" "Copy resource name" kubel-copy-resource-name) - ("l" "Copy pod log command" kubel-copy-log-command) - ("p" "Copy command prefix" kubel-copy-command-prefix) - ("C" "Copy last command" kubel-copy-last-command)]) - -(define-transient-command kubel-delete-popup () - "Kubel Delete menu" - ["Arguments" - ("-f" "Force" "--force --grace-period=0")] - ["Actions" - ("k" "Delete resource(s)" kubel-delete-resource)]) - -(define-transient-command kubel-describe-popup () - "Kubel Describe Menu" - ["Arguments" - ("-y" "Yaml" "-o yaml")] - ["Actions" - ("RET" "Describe" kubel-get-resource-details)]) - -(define-transient-command kubel-help-popup () - "Kubel Menu" - [["Actions" - ;; global - ("RET" "Resource details" kubel-describe-popup) - ("E" "Quick edit" kubel-quick-edit) - ("g" "Refresh" kubel) - ("k" "Delete" kubel-delete-popup) - ("r" "Rollout" kubel-rollout-history)] - ["" ;; based on current view - ("p" "Port forward" kubel-port-forward-pod) - ("l" "Logs" kubel-log-popup) - ("e" "Exec" kubel-exec-popup) - ("j" "Jab" kubel-jab-deployment)] - ["Settings" - ("C" "Set context" kubel-set-context) - ("n" "Set namespace" kubel-set-namespace) - ("R" "Set resource" kubel-set-resource) - ("K" "Set kubectl config file" kubel-set-kubectl-config-file) - ("F" "Set output format" kubel-set-output-format)] - ["Filter" - ("f" "Filter" kubel-set-filter) - ("M-n" "Next highlight" kubel-jump-to-next-highlight) - ("M-p" "Previous highlight" kubel-jump-to-previous-highlight)] - ["Marking" - ("m" "Mark item" kubel-mark-item) - ("u" "Unmark item" kubel-unmark-item) - ("M" "Mark all items" kubel-mark-all) - ("U" "Unmark all items" kubel-unmark-all)] - ["Utilities" - ("c" "Copy to clipboad..." kubel-copy-popup) - ("$" "Show Process buffer" kubel-show-process-buffer)]]) - -;; mode map -(defvar kubel-mode-map - (let ((map (make-sparse-keymap))) - ;; global - (define-key map (kbd "RET") 'kubel-get-resource-details) - (define-key map (kbd "K") 'kubel-set-kubectl-config-file) - (define-key map (kbd "C") 'kubel-set-context) - (define-key map (kbd "n") 'kubel-set-namespace) - (define-key map (kbd "g") 'kubel) - (define-key map (kbd "h") 'kubel-help-popup) - (define-key map (kbd "?") 'kubel-help-popup) - (define-key map (kbd "F") 'kubel-set-output-format) - (define-key map (kbd "R") 'kubel-set-resource) - (define-key map (kbd "k") 'kubel-delete-popup) - (define-key map (kbd "f") 'kubel-set-filter) - (define-key map (kbd "r") 'kubel-rollout-history) - (define-key map (kbd "E") 'kubel-quick-edit) - (define-key map (kbd "M-n") 'kubel-jump-to-next-highlight) - (define-key map (kbd "M-p") 'kubel-jump-to-previous-highlight) - (define-key map (kbd "$") 'kubel-show-process-buffer) - ;; based on view - (define-key map (kbd "p") 'kubel-port-forward-pod) - (define-key map (kbd "l") 'kubel-log-popup) - (define-key map (kbd "c") 'kubel-copy-popup) - (define-key map (kbd "e") 'kubel-exec-popup) - (define-key map (kbd "j") 'kubel-jab-deployment) - - (define-key map (kbd "m") 'kubel-mark-item) - (define-key map (kbd "u") 'kubel-unmark-item) - (define-key map (kbd "M") 'kubel-mark-all) - (define-key map (kbd "U") 'kubel-unmark-all) - - map) - "Keymap for `kubel-mode'.") - -(defvar kubel-last-position nil) - -;;;###autoload -(defun kubel (&optional directory) - "Invoke the kubel buffer. - -DIRECTORY is optional for TRAMP support." - (interactive) - (kubel--save-line) - (kubel--pop-to-buffer (kubel--buffer-name)) - (when directory (setq default-directory directory)) - (kubel-mode) - (message (concat "Namespace: " kubel-namespace))) - -(define-derived-mode kubel-mode tabulated-list-mode "Kubel" - "Special mode for kubel buffers." - (buffer-disable-undo) - (kill-all-local-variables) - (setq truncate-lines t) - (setq mode-name "Kubel") - (setq major-mode 'kubel-mode) - (use-local-map kubel-mode-map) - (let ((entries (kubel--populate-list))) - (setq tabulated-list-format (car entries)) - (setq tabulated-list-entries (cadr entries))) ; TODO handle "No resource found" - (setq tabulated-list-sort-key kubel--list-sort-key) - (setq tabulated-list-sort-key nil) - (tabulated-list-init-header) - (tabulated-list-print) - (hl-line-mode 1) - (run-mode-hooks 'kubel-mode-hook)) - -(add-hook 'kubel-mode-hook #'kubel--jump-back-to-line) - -(provide 'kubel) -;;; kubel.el ends here diff --git a/elpa/kubel-20220509.104/kubel-autoloads.el b/elpa/kubel-20220509.104/kubel-autoloads.el new file mode 100644 index 00000000..b099ed0d --- /dev/null +++ b/elpa/kubel-20220509.104/kubel-autoloads.el @@ -0,0 +1,29 @@ +;;; kubel-autoloads.el --- automatically extracted autoloads -*- lexical-binding: t -*- +;; +;;; Code: + +(add-to-list 'load-path (directory-file-name + (or (file-name-directory #$) (car load-path)))) + + +;;;### (autoloads nil "kubel" "kubel.el" (0 0 0 0)) +;;; Generated autoloads from kubel.el + +(autoload 'kubel "kubel" "\ +Invoke the kubel buffer. + +DIRECTORY is optional for TRAMP support. + +\(fn &optional DIRECTORY)" t nil) + +(register-definition-prefixes "kubel" '("kubel-")) + +;;;*** + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; coding: utf-8 +;; End: +;;; kubel-autoloads.el ends here diff --git a/elpa/kubel-20220509.104/kubel-pkg.el b/elpa/kubel-20220509.104/kubel-pkg.el new file mode 100644 index 00000000..683f598f --- /dev/null +++ b/elpa/kubel-20220509.104/kubel-pkg.el @@ -0,0 +1,2 @@ +;;; Generated package description from kubel.el -*- no-byte-compile: t -*- +(define-package "kubel" "20220509.104" "Control Kubernetes with limited permissions" '((transient "0.1.0") (emacs "25.3") (dash "2.12.0") (s "1.2.0") (yaml-mode "0.0.14")) :commit "c45e19a215e8e7df80a61c10ca1fa26dcfd1de35" :authors '(("Adrien Brochard")) :maintainer '("Adrien Brochard") :keywords '("kubernetes" "k8s" "tools" "processes") :url "https://github.com/abrochard/kubel") diff --git a/elpa/kubel-20220509.104/kubel.el b/elpa/kubel-20220509.104/kubel.el new file mode 100644 index 00000000..b58d7360 --- /dev/null +++ b/elpa/kubel-20220509.104/kubel.el @@ -0,0 +1,1256 @@ +;;; kubel.el --- Control Kubernetes with limited permissions -*- lexical-binding: t; -*- + +;; Copyright (C) 2018, Adrien Brochard + +;; This file is NOT part of Emacs. + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of the +;; License, or (at your option) any later version. + + + +;; This program is distributed in the hope that it will be useful, but +;; WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;; General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program; if not, write to the Free Software +;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +;; USA + +;; Version: 1.0 +;; Package-Version: 20220509.104 +;; Package-Commit: c45e19a215e8e7df80a61c10ca1fa26dcfd1de35 +;; Author: Adrien Brochard +;; Keywords: kubernetes k8s tools processes +;; URL: https://github.com/abrochard/kubel +;; License: GNU General Public License >= 3 +;; Package-Requires: ((transient "0.1.0") (emacs "25.3") (dash "2.12.0") (s "1.2.0") (yaml-mode "0.0.14")) + +;;; Commentary: + +;; Emacs extension for controlling Kubernetes with limited permissions. + +;;; Usage: + +;; To list the pods in your current context and namespace, call +;; +;; M-x kubel +;; +;; To set the path to the kubectl config file, call: +;; M-x kubel-set-kubectl-config-file +;; +;; or +;; +;; (kubel-set-kubectl-config-file ) +;; ex: (kubel-set-kubectl-config-file "~/.kube/another-config") +;; +;; To set said namespace and context, respectively call +;; +;; M-x kubel-set-namespace +;; M-x kubel-set-context +;; +;; Note that namespace will autocomplete but not context, +;; this is because I interact with kubernetes through a user who +;; does not have permissions to list namespaces. +;; +;; To switch to showing a different resource, use the `R` command or +;; +;; M-x kubel-set-resource +;; +;; This will let you select a resource and re-display the kubel buffer. + +;;; Shortcuts: + +;; On the kubel screen, place your cursor on a resource +;; +;; enter => get resource details +;; C-u enter => describe resource +;; h => help popup +;; ? => help popup +;; E => quick edit any resource +;; g => refresh +;; k => delete popup +;; r => see the rollout history for resource +;; p => port forward pod +;; l => log popup +;; e => exec popup +;; j => jab deployment to force rolling update +;; S => scale replicas +;; C => set context +;; n => set namespace +;; R => set resource +;; K => set kubectl config file +;; F => set output format +;; f => set a substring filter for resource name +;; M-n => jump to the next highlighted resource +;; M-p => jump to previous highlighted resource +;; m => mark item +;; u => unmark item +;; M => mark all items +;; U => unmark all items +;; c => copy popup +;; $ => show process buffer +;; s => show only resources with specified label value + +;;; Customize: + +;; By default, kubel log tails from the last 100 lines, you can change the `kubel-log-tail-n` variable to set another line number. + +;;; Code: + +(require 'transient) +(require 'dash) +(require 's) +(require 'yaml-mode) +(require 'tramp) +(require 'subr-x) +(require 'eshell) +(require 'dired) + +(defgroup kubel nil "Customisation group for kubel." + :group 'extensions) + +(defconst kubel--list-sort-key + '("NAME" . nil) + "Sort table on this key.") + +(defconst kubel--status-colors + '(("Running" . "green") + ("Healthy" . "green") + ("Active" . "green") + ("Ready" . "green") + ("True" . "green") + ("Unknown" . "orange") + ("Error" . "red") + ("Evicted" . "red") + ("MemoryPressure" . "red") + ("PIDPressure" . "red") + ("DiskPressure" . "red") + ("RevisionMissing" . "red") + ("RevisionFailed" . "red") + ("NetworkUnavailable" . "red") + ("Completed" . "yellow") + ("CrashLoopBackOff" . "red") + ("Terminating" . "blue")) + "Associative list of status to color.") + +(defcustom kubel-kubectl "kubectl" + "Kubectl binary path." + :type 'string + :group 'kubel) + +(defconst kubel--process-buffer "*kubel-process*" + "Kubel process buffer name.") + +(defcustom kubel-output "yaml" + "Format for output: json|yaml|wide|custom-columns=..." + :type 'string + :group 'kubel) + +(defcustom kubel-log-tail-n 100 + "Default number of lines to tail." + :type 'integer + :group 'kubel) + +(defcustom kubel-use-namespace-list 'auto + "Control behavior for namespace completion. + +auto - default, use `kubectl auth can-i list namespace` to determine if we can list namespaces +on - always assume we can list namespaces +off - always assume we cannot list namespaces" + :type 'symbol + :group 'kubel + :options '('auto 'on 'off)) + +(defun kubel--append-to-process-buffer (str) + "Append string STR to the process buffer." + (with-current-buffer (get-buffer-create kubel--process-buffer) + (read-only-mode -1) + (goto-char (point-max)) + (insert (format "%s\n" str)))) + +(defvar kubel--last-command nil) + +(defun kubel--log-command (process-name cmd) + "Log the kubectl command to the process buffer. + +PROCESS-NAME is the name of the process. +CMD is the kubectl command as a list." + (let ((str-cmd (if (equal 'string (type-of cmd)) cmd (mapconcat #'identity cmd " ")))) + (setq kubel--last-command str-cmd) + (kubel--append-to-process-buffer + (format "[%s]\ncommand: %s" process-name str-cmd)))) + +(defun kubel--exec-to-string (cmd) + "Replace \"shell-command-to-string\" to log to process buffer. + +CMD is the command string to run." + (kubel--log-command "kubectl-command" cmd) + (shell-command-to-string cmd)) + +(defvar kubel-namespace "default" + "Current namespace.") + +(defvar kubel-resource "Pods" + "Current resource.") + +(defvar kubel-context + (replace-regexp-in-string + "\n" "" (kubel--exec-to-string "kubectl config current-context")) + "Current context. Tries to smart default.") + +(defvar kubel-resource-filter "" + "Substring filter for resource name.") + +(defvar kubel-selector "" + "Label selector for resources.") + +(defvar kubel--line-number nil + "Store the current line number to jump back after a refresh.") + +(defvar kubel-namespace-history '() + "List of previously used namespaces.") + +(defvar kubel-selector-history '() + "List of previously used selectors.") + +;; fallback list of resources if the version of kubectl doesn't support api-resources command +(defvar kubel-kubernetes-resources-list + '("Pods" + "Services" + "Namespaces" + "Nodes" + "Configmaps" + "Secrets" + "Bindings" + "PersistentVolumeClaims" + "PersistentVolumes" + "ReplicationControllers" + "ResourceQuotas" + "ServiceAccounts" + "Deployments" + "DaemonSets" + "ReplicaSets" + "StatefulSets" + "Jobs" + "hpa" + "Images" + "Ingresses" + "ClusterRoles" + "RoleBindings" + "Roles")) + +(defvar kubel--kubernetes-version-cached nil) + +(defvar kubel--kubernetes-resources-list-cached nil) + +(defvar kubel--can-get-namespace-cached nil) + +(defvar kubel--namespace-list-cached nil) + +(defvar kubel--label-values-cached nil) + +(defvar kubel--selected-items '()) + +(defun kubel--invalidate-context-caches () + "Invalidate the context caches." + (setq kubel--kubernetes-resources-list-cached nil) + (setq kubel--kubernetes-version-cached nil) + (setq kubel--can-get-namespace-cached nil) + (setq kubel--namespace-list-cached nil) + (setq kubel--label-values-cached nil)) + +(defun kubel-kubernetes-version () + "Return a list with (major-version minor-version patch)." + (let ((version-string (if (null kubel--kubernetes-version-cached) + (setq kubel--kubernetes-version-cached + (kubel--exec-to-string "kubectl version")) + kubel--kubernetes-version-cached))) + (string-match "GitVersion:\"v\\([0-9]*\\)\.\\([0-9]*\\)\.\\([0-9]*\\)[^0-9].*\"" version-string) + (list + (string-to-number (match-string 1 version-string)) + (string-to-number (match-string 2 version-string)) + (string-to-number (match-string 3 version-string))))) + +(defun kubel-kubernetes-compatible-p (version) + "Return TRUE if kubernetes version is greater than or equal to VERSION. +VERSION should be a list of (major-version minor-version patch)." + (let* + ((kubernetes-version (kubel-kubernetes-version)) + (kubernetes-major-version (nth 0 kubernetes-version)) + (kubernetes-minor-version (nth 1 kubernetes-version)) + (kubernetes-patch-version (nth 2 kubernetes-version))) + (and + (<= (nth 0 version) kubernetes-major-version) + (or (<= (nth 1 version) kubernetes-minor-version) (< (nth 0 version) kubernetes-major-version)) + (or (<= (nth 2 version) kubernetes-patch-version) (< (nth 1 version) kubernetes-minor-version))))) + +(defun kubel--populate-list () + "Return a list with a tabulated list format and \"tabulated-list-entries\"." + (let* ((body (kubel--exec-to-string (concat (kubel--get-command-prefix) " get " kubel-resource))) + (entrylist (kubel--parse-body body))) + (when (string-prefix-p "No resources found" body) + (message "No resources found")) ;; TODO exception here + (list (kubel--get-list-format entrylist) (kubel--get-list-entries entrylist)))) + +(defun kubel--age-to-secs (age) + "Convert AGE in format 1d2h3m4s to seconds." + (let ((rex (rx bol + (opt (group (one-or-more digit)) "d") + (opt (group (one-or-more digit)) "h") + (opt (group (one-or-more digit)) "m") + (opt (group (one-or-more digit)) "s") + eol))) + (if (string-match rex age) + (-sum (--map-indexed + (* (--if-let (match-string (1+ it-index) age) + (string-to-number it) + 0) + it) + '(86400 3600 60 1))) + 0))) + +(defun kubel--make-age-comparator (colnum) + "Return a function that compares two ages at given column COLNUM." + (lambda (row1 row2) + (let ((age1 (elt (cadr row1) colnum)) + (age2 (elt (cadr row2) colnum))) + (< (kubel--age-to-secs age1) + (kubel--age-to-secs age2))))) + +(defun kubel--column-entry (entrylist) + "Return a function of colnum to retrieve an entry in a given column for ENTRYLIST." + (function + (lambda (colnum) + (let* ((name (kubel--column-header entrylist colnum)) + (width (+ 4 (kubel--column-width entrylist colnum))) + (sort (if (member name '("AGE" "DURATION" "LAST SCHEDULE")) + (kubel--make-age-comparator colnum) + t))) + (list name width sort))))) + +(defun kubel--get-list-format (entrylist) + "Get the list format. + +ENTRYLIST is the output of the parsed body." + (defun kubel--get-column-entry (colnum) + (let ((kubel--get-entry (kubel--column-entry entrylist))) + (funcall kubel--get-entry colnum))) + (cl-map 'vector #'kubel--get-column-entry (number-sequence 0 (- (kubel--ncols entrylist) 1)))) + +(defun kubel--update-selected-items (entries) + "Check that all selected items still exist. + +ENTRIES are all resources." + (dolist (i (-difference kubel--selected-items (mapcar #'car entries))) + (setq kubel--selected-items (delete i kubel--selected-items)))) + +(defun kubel--get-list-entries (entrylist) + "Get the entries. + +ENTRYLIST is the output of the parsed body." + (let ((entries (cdr entrylist))) + (kubel--update-selected-items entries) + (mapcar (lambda (x) + (list (car x) + (vconcat [] (mapcar #'kubel--propertize-status x)))) + entries))) + +(defun kubel--parse-body (body) + "Parse the body of kubectl get resource call into a list. + +BODY is the raw output of kubectl get resource." + (let* ((lines (nbutlast (split-string body "\n"))) + (header (car lines)) + ;; Cronjobs have a "LAST SCHEDULE" column, so need to split on 2+ whitespace chars. + (starts (cl-loop for start = 0 then (match-end 0) + while (string-match (rx (>= 2 whitespace)) header start) + collect (match-end 0))) + (position (-zip-with 'cons (cons 0 starts) (append starts '("end")))) + (parse-line (lambda (line) + (mapcar (lambda (pos) + (kubel--extract-value line (car pos) (cdr pos))) + position)))) + (mapcar parse-line lines))) + +(defun kubel--extract-value (line min max) + "Extract value from LINE between MIN and MAX. +If it's just white space, return -, else trim space. +If MAX is the end of the line, dynamically adjust." + (let* ((maxx (if (equal max "end") (length line) max)) + (str (substring-no-properties line min maxx))) + (if (string-match "^ +$" str) + "-" + (string-trim str)))) + +(defun kubel--ncols (entrylist) + "Return the number of columns in ENTRYLIST." + (length (car entrylist))) + +(defun kubel--nrows (entrylist) + "Return the nubmer of rows in ENTRYLIST." + (length entrylist)) + +(defun kubel--column-header (entrylist colnum) + "Return the header for a specific COLNUM in ENTRYLIST." + (nth colnum (car entrylist))) + +(defun kubel--column-width (entrylist colnum) + "Return the width of a specific COLNUM in ENTRYLIST." + (seq-max (mapcar (lambda (x) (length (nth colnum x) )) entrylist))) + +(defun kubel--buffer-name () + "Return kubel buffer name." + (concat (format "*kubel (%s) [%s]: %s" kubel-namespace kubel-context kubel-resource) + (unless (equal kubel-selector "") + (format " (%s)" kubel-selector)) + "*")) + +(defun kubel--items-selected-p () + "Return non-nil if there are items selected." + (>= (length kubel--selected-items) 1)) + +(defun kubel--propertize-status (status) + "Return the status in proper font color. + +STATUS is the pod status string." + (let ((pair (cdr (assoc status kubel--status-colors))) + (match (or (equal kubel-resource-filter "") (string-match-p kubel-resource-filter status))) + (selected (and (kubel--items-selected-p) (-contains? kubel--selected-items status)))) + (cond (pair (propertize status 'font-lock-face `(:foreground ,pair))) + (selected (propertize (concat "*" status) 'face 'dired-marked)) + ((not match) (propertize status 'font-lock-face '(:foreground "darkgrey"))) + (t status)))) + +(defun kubel--pop-to-buffer (name) + "Utility function to pop to buffer or create it. + +NAME is the buffer name." + (unless (get-buffer name) + (get-buffer-create name)) + (pop-to-buffer-same-window name)) + +(defun kubel--process-error-buffer (process-name) + "Return the error buffer name for the PROCESS-NAME." + (format "*%s:err*" process-name)) + +(defun kubel--sentinel (process _) + "Sentinel function for PROCESS." + (let ((process-name (process-name process)) + (exit-status (process-exit-status process))) + (kubel--append-to-process-buffer (format "[%s]\nexit-code: %s" process-name exit-status)) + (unless (eq 0 exit-status) + (let ((err (with-current-buffer (kubel--process-error-buffer process-name) + (buffer-string)))) + (kubel--append-to-process-buffer (format "error: %s" err)) + (error (format "Kubel process %s error: %s" process-name err)))))) + +(defun kubel--exec (process-name args &optional readonly) + "Utility function to run commands in the proper context and namespace. + +PROCESS-NAME is an identifier for the process. Default to \"kubel-command\". +ARGS is a ist of arguments. +READONLY If true buffer will be in readonly mode(view-mode)." + (when (equal process-name "") + (setq process-name "kubel-command")) + (let ((buffer-name (format "*%s*" process-name)) + (error-buffer (kubel--process-error-buffer process-name)) + (cmd (append (list kubel-kubectl) (kubel--get-context-namespace) args))) + (when (get-buffer buffer-name) + (kill-buffer buffer-name)) + (when (get-buffer error-buffer) + (kill-buffer error-buffer)) + (kubel--log-command process-name cmd) + (make-process :name process-name + :buffer buffer-name + :sentinel #'kubel--sentinel + :file-handler t + :stderr error-buffer + :command cmd) + (pop-to-buffer buffer-name) + (if readonly + (with-current-buffer buffer-name + (view-mode))))) + +(defun kubel--get-resource-under-cursor () + "Utility function to get the name of the resource under the cursor. +Strip the `*` prefix if the resource is selected" + (replace-regexp-in-string + "^\*" "" (aref (tabulated-list-get-entry) 0))) + +(defun kubel--get-context-namespace () + "Utility function to return the proper context and namespace arguments." + (append + (unless (equal kubel-context "") + (list "--context" kubel-context)) + (unless (equal kubel-namespace "default") + (list "-n" kubel-namespace)))) + +(defun kubel--get-selector () + "Utility function to return current label selector." + (unless (equal kubel-selector "") + (list "--selector" kubel-selector))) + +(defun kubel--get-command-prefix () + "Utility function to prefix the kubectl command with proper context and namespace." + (mapconcat 'identity (append (list kubel-kubectl) (kubel--get-context-namespace) (kubel--get-selector)) " ")) + +(defun kubel--get-containers (pod-name &optional type) + "List the containers in a pod. + +POD-NAME is the name of the pod. +TYPE is containers or initContainers." + (unless type (setq type "containers")) + (split-string + (kubel--exec-to-string + (format "%s get pod %s -o jsonpath='{.spec.%s[*].name}'" (kubel--get-command-prefix) pod-name type)) " ")) + +(defun kubel--get-pod-labels () + "List labels of pods in a current namespace." + (let* ((raw-labels + (split-string + (replace-regexp-in-string + (regexp-quote ":") "=" + (replace-regexp-in-string + "map\\[\\(.+?\\)\\]" "\\1" + (kubel--exec-to-string + (format "%s get pod -o jsonpath='{.items[*].metadata.labels}'" (kubel--get-command-prefix))))))) + (splitted (mapcan (lambda (s) (split-string s ",")) + raw-labels)) + (cleaned (mapcar (lambda (s) (replace-regexp-in-string "[{|\"|}]" "" s)) splitted)) + (unique (-distinct cleaned))) + unique)) + +(defun kubel--select-resource (name) + "Prompt user to select an instance out of a list of resources. + +NAME is the string name of the resource." + (let ((cmd (format "%s get %s -o=jsonpath='{.items[*].metadata.name}'" + (kubel--get-command-prefix) name))) + (completing-read (concat (s-upper-camel-case name) ": ") + (split-string (kubel--exec-to-string cmd) " ")))) + +(defun kubel--describe-resource (name &optional describe) + "Describe a specific resource. + +NAME is the string name of the resource to decribe. +DESCRIBE is boolean to describe instead of get resource details" + (let* ((resource (kubel--select-resource name)) + (process-name (format "kubel - %s - %s" name resource))) + (if describe + (kubel--exec process-name (list "describe" name resource)) + (kubel--exec process-name (list "get" name "-o" kubel-output resource))) + (when (string-equal kubel-output "yaml") + (yaml-mode) + (kubel-yaml-editing-mode)) + (goto-char (point-min)))) + +(defun kubel--show-rollout-revision (type name) + "Show a specific revision of a certain resource. + +TYPE is the resource type. +NAME is the resource name." + (let* ((typename (format "%s/%s" type name)) + (revision (car (split-string (kubel--select-rollout typename)))) + (process-name (format "kubel - rollout - %s - %s" typename revision))) + (kubel--exec process-name + (list "rollout" "history" typename (format "--revision=%s" revision))) + (goto-char (point-min)))) + +(defun kubel--list-rollout (typename) + "Return a list of revisions with format '%number %cause'. + +TYPENAME is the resource type/name." + (let ((cmd (format "%s rollout history %s" (kubel--get-command-prefix) typename))) + (nthcdr 2 (split-string (kubel--exec-to-string cmd) "\n" t)))) + +(defun kubel--select-rollout (typename) + "Select a rollout version. + +TYPENAME is the resource type/name." + (let ((prompt (format "Select a rollout of %s: " typename)) + (rollouts (kubel--list-rollout typename))) + (completing-read prompt rollouts))) + +(defun kubel--is-pod-view () + "Return non-nil if this is the pod view." + (equal (capitalize kubel-resource) "Pods")) + +(defun kubel--is-deployment-view () + "Return non-nil if this is the pod view." + (-contains? '("Deployments" "deployments" "deployments.apps") kubel-resource)) + +(defun kubel--is-scalable () + "Return non-nil if the resource can be scaled." + (or + (kubel--is-deployment-view) + (-contains? '("ReplicaSets" "replicasets" "replicasets.apps") kubel-resource) + (-contains? '("StatefulSets" "statefulsets" "statefulsets.apps") kubel-resource))) + +(defun kubel--save-line () + "Save the current line number if the view is unchanged." + (if (equal (buffer-name (current-buffer)) + (kubel--buffer-name)) + (setq kubel--line-number (+ 1 (count-lines 1 (point)))) + (setq kubel--line-number nil))) + +(defun kubel--jump-back-to-line () + "Jump back to the last cached line number." + (when kubel--line-number + (goto-line kubel--line-number))) + +;; interactive +(define-minor-mode kubel-yaml-editing-mode + "Kubel Yaml editing mode. +Use C-c C-c to kubectl apply the current yaml buffer." + :init-value nil + :keymap (let ((map (make-sparse-keymap))) + (define-key map (kbd "C-c C-c") 'kubel-apply) + map)) + +(defun kubel-apply () + "Save the current buffer to a temp file and try to kubectl apply it." + (interactive) + (setq dir-prefix (or + (when (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (format "/%s%s:%s@%s:" (or hop "") method user host))) + "")) + + (let* ((filename-without-tramp-prefix (format "/tmp/kubel/%s-%s.yaml" + (replace-regexp-in-string "\*\\| " "" (buffer-name)) + (floor (float-time)))) + (filename (format "%s%s" dir-prefix filename-without-tramp-prefix))) + (when (y-or-n-p "Apply the changes? ") + (unless (file-exists-p (format "%s/tmp/kubel" dir-prefix)) + (make-directory (format "%s/tmp/kubel" dir-prefix) t)) + (write-region (point-min) (point-max) filename) + (kubel--exec (format "kubectl - apply - %s" filename) (list "apply" "-f" filename-without-tramp-prefix)) + (message "Applied %s" filename)))) + +(defun kubel-get-resource-details (&optional describe) + "Get the details of the resource under the cursor. + + DESCRIBE is the optional param to describe instead of get." + (interactive "P") + (let* ((resource (kubel--get-resource-under-cursor)) + (process-name (format "kubel - %s - %s" kubel-resource resource))) + (if describe + (kubel--exec process-name (list "describe" kubel-resource (kubel--get-resource-under-cursor))) + (kubel--exec process-name (list "get" kubel-resource (kubel--get-resource-under-cursor) "-o" kubel-output))) + (when (or (string-equal kubel-output "yaml") (transient-args 'kubel-describe-popup)) + (yaml-mode) + (kubel-yaml-editing-mode)) + (goto-char (point-min)))) + + +(defun kubel--default-tail-arg (args) + "Ugly function to make sure that there is at least the default tail. + +ARGS is the arg list from transient." + (if (car (remove nil (mapcar (lambda (x) + (string-prefix-p "--tail=" x)) args))) + args + (append args (list (concat "--tail=" (format "%s" kubel-log-tail-n)))))) + +(defun kubel-get-pod-logs (&optional args type) + "Get the last N logs of the pod under the cursor. + +ARGS is the arguments list from transient. +TYPE is containers or initContainers." + (interactive + (list (transient-args 'kubel-log-popup))) + (dolist (pod (if (kubel--is-pod-view) + (if (kubel--items-selected-p) + kubel--selected-items + (list (kubel--get-resource-under-cursor))) + (list (kubel--select-resource "Pods")))) + (let* ((type (or type "containers")) + (containers (kubel--get-containers pod type)) + (container (if (equal (length containers) 1) + (car containers) + (completing-read "Select container: " containers))) + (process-name (format "kubel - logs - %s - %s" pod container))) + (kubel--exec process-name + (append '("logs") (kubel--default-tail-arg args) (list pod container)) t)))) + +(defun kubel-get-pod-logs--initContainer (&optional args) + "Get the last N logs of the pod under the cursor. + +ARGS is the arguments list from transient." + (interactive + (list (transient-args 'kubel-log-popup))) + (kubel-get-pod-logs args "initContainers")) + +(defun kubel-get-logs-by-labels (&optional args) + "Get the last N logs of the pods by labels. +ARGS is the arguments list from transient." + (interactive + (list (transient-args 'kubel-log-popup))) + (let* ((labels (kubel--get-pod-labels)) + (label (completing-read "Select container: " labels)) + (process-name (format "kubel - logs - %s" label))) + (kubel--exec process-name + (append '("logs") (kubel--default-tail-arg args) '("-l") (list label)) t))) + +(defun kubel-copy-resource-name () + "Copy the name of the pod under the cursor." + (interactive) + (kill-new (kubel--get-resource-under-cursor)) + (message "Resource name copied to kill-ring")) + +(defun kubel-copy-log-command () + "Copy the streaming log command of the pod under the cursor." + (interactive) + (kill-new + (format "%s logs -f --tail=%s %s" + (kubel--get-command-prefix) + kubel-log-tail-n + (if (kubel--is-pod-view) + (kubel--get-resource-under-cursor) + (kubel--select-resource "Pods")))) + (message "Log command copied to kill-ring")) + +(defun kubel-copy-command-prefix () + "Copy the kubectl command prefix." + (interactive) + (kill-new (kubel--get-command-prefix)) + (message "Command prefix copied to kill-ring")) + +(defun kubel-copy-last-command () + "Copy the last kubectl command ran." + (interactive) + (kill-new kubel--last-command) + (message (concat "Last command copied: " kubel--last-command))) + +(defun kubel-set-kubectl-config-file (configfile) + "Set the path to the kubectl CONFIGFILE." + (interactive "f") + (let ((configfile (or configfile "~/.kube/config"))) + (if (file-exists-p (expand-file-name configfile)) + (setenv "KUBECONFIG" (expand-file-name configfile)) + (error "Kubectl config file '%s' does not exist!" configfile)))) + +(defun kubel--can-get-namespace () + "Determine if permissions allow for `kubectl get namespace` in current context." + (cond ((eq kubel-use-namespace-list 'on) t) + ((eq kubel-use-namespace-list 'auto) + (progn + (unless kubel--can-get-namespace-cached + (setq kubel--can-get-namespace-cached + (string-match-p "yes\n" + (kubel--exec-to-string + (format "%s --context %s auth can-i list namespaces" kubel-kubectl kubel-context)))))) + kubel--can-get-namespace-cached))) + +(defun kubel--get-namespace () + "Get namespaces for current context, try to recover from cache first." + (unless kubel--namespace-list-cached + (setq kubel--namespace-list-cached + (split-string (kubel--exec-to-string + (format "%s --context %s get namespace -o jsonpath='{.items[*].metadata.name}'" kubel-kubectl kubel-context)) " "))) + kubel--namespace-list-cached) + +(defun kubel--list-namespace () + "List namespace, either from history, or dynamically if possible." + (if (kubel--can-get-namespace) + (kubel--get-namespace) + kubel-namespace-history)) + +(defun kubel--add-namespace-to-history (namespace) + "Add NAMESPACE to history if it isn't there already." + (unless (member namespace kubel-namespace-history) + (push namespace kubel-namespace-history))) + +(defun kubel-set-namespace () + "Set the namespace." + (interactive) + (let* ((namespace (completing-read "Namespace: " (kubel--list-namespace) + nil nil nil nil "default")) + (kubel--buffer (get-buffer (kubel--buffer-name))) + (last-default-directory (when kubel--buffer + (with-current-buffer kubel--buffer default-directory)))) + (when kubel--buffer (kill-buffer kubel--buffer)) + (setq kubel-namespace namespace) + (kubel--add-namespace-to-history namespace) + (kubel last-default-directory))) + +(defun kubel-set-context () + "Set the context." + (interactive) + (let* ((kubel--buffer (get-buffer (kubel--buffer-name))) + (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) + (when kubel--buffer (kill-buffer kubel--buffer));; kill buffer for previous context if possible + (setq kubel-context + (completing-read + "Select context: " + (split-string (kubel--exec-to-string (format "%s config view -o jsonpath='{.contexts[*].name}'" kubel-kubectl)) " "))) + (kubel--invalidate-context-caches) + (setq kubel-namespace "default") + (kubel last-default-directory))) + +(defun kubel--add-selector-to-history (selector) + "Add SELECTOR to history if it isn't there already." + (unless (member selector kubel-selector-history) + (push selector kubel-selector-history))) + +(defun kubel--get-all-selectors () + "Get all selectors." + (unless kubel--label-values-cached + (let ((labels (kubel--get-pod-labels))) + (setq kubel--label-values-cached labels))) + kubel--label-values-cached) + +(defun kubel--list-selectors () + "List selector expressions from history." + (delete-dups + (append '("none") (kubel--get-all-selectors) + kubel-selector-history))) + +(defun kubel-set-label-selector () + "Set the selector." + (interactive) + (let ((selector (completing-read + "Selector: " + (kubel--list-selectors)))) + (when (equal selector "none") + (setq selector "")) + (setq kubel-selector selector)) + (kubel--add-selector-to-history kubel-selector) ; Update pod list according to the label selector + (kubel)) + +(defun kubel--fetch-api-resource-list () + "Fetch the API resource list." + (split-string (kubel--exec-to-string + (format "%s --context %s api-resources -o name --no-headers=true" kubel-kubectl kubel-context)) "\n" t)) + +(defun kubel-set-resource (&optional refresh) + "Set the resource. +If called with a prefix argument REFRESH, refreshes +the context caches, including the cached resource list." + (interactive "P") + (when refresh (kubel--invalidate-context-caches)) + (let* ((current-buffer-name (kubel--buffer-name)) + (resource-list (if (kubel-kubernetes-compatible-p '(1 13 3)) + (if (null kubel--kubernetes-resources-list-cached) + (setq kubel--kubernetes-resources-list-cached + (kubel--fetch-api-resource-list)) + kubel--kubernetes-resources-list-cached) + kubel-kubernetes-resources-list)) + (kubel--buffer (get-buffer current-buffer-name)) + (last-default-directory (when kubel--buffer (with-current-buffer kubel--buffer default-directory)))) + (setq kubel-resource + (completing-read "Select resource: " resource-list)) + (when kubel--buffer (kill-buffer kubel--buffer)) ;; kill buffer for previous context if possible + (kubel last-default-directory))) + +(defun kubel-set-output-format () + "Set output format of kubectl." + (interactive) + (setq kubel-output + (completing-read + "Set output format: " + '("yaml" "json" "wide" "custom-columns="))) + (kubel)) + +(defun kubel-port-forward-pod (p) + "Port forward a pod to your local machine. + +P can be a single number or a localhost:container port pair." + (interactive "sPort: ") + (let* ((port (if (string-match-p ":" p) p (format "%s:%s" p p))) + (pod (if (kubel--is-pod-view) + (kubel--get-resource-under-cursor) + (kubel--select-resource "Pods"))) + (process-name (format "kubel - port-forward - %s:%s" pod port))) + (kubel--exec process-name (list "port-forward" pod port)))) + +(defun kubel-setup-tramp () + "Setup a kubectl TRAMP." + (setq tramp-methods (delete (assoc "kubectl" tramp-methods) tramp-methods)) ;; cleanup previous tramp method + ;; TODO error message if resource is not pod + (add-to-list 'tramp-methods + `("kubectl" + (tramp-login-program ,kubel-kubectl) + (tramp-login-args (,(kubel--get-context-namespace) ("exec" "-it") ("-c" "%u") ("%h") ("sh"))) + (tramp-remote-shell "sh") + (tramp-remote-shell-args ("-i" "-c"))))) ;; add the current context/namespace to tramp methods + +(defun kubel--get-container-under-cursor () + "Get `(container . pod)' name under cursor." + (let* ((pod (if (kubel--is-pod-view) + (kubel--get-resource-under-cursor) + (kubel--select-resource "Pods"))) + (containers (kubel--get-containers pod)) + (container (if (equal (length containers) 1) + (car containers) + (completing-read "Select container: " containers)))) + (cons container pod))) + +(defun kubel-exec-pod () + "Exec into the pod under the cursor -> `find-file." + (interactive) + (kubel-setup-tramp) + (let* ((dir-prefix (or + (when (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (format "%s%s:%s@%s|" (or hop "") method user host)))"")) + (con-pod (kubel--get-container-under-cursor))) + (find-file (format "/%skubectl:%s@%s:/" dir-prefix (car con-pod) (cdr con-pod))))) + +(defun kubel-exec-shell-pod () + "Exec into the pod under the cursor -> shell." + (interactive) + (kubel-setup-tramp) + (let* ((dir-prefix (or + (when (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (format "%s%s:%s@%s|" (or hop "") method user host))) "")) + (con-pod (kubel--get-container-under-cursor)) + (container (car con-pod)) + (pod (cdr con-pod)) + (default-directory (format "/%skubectl:%s@%s:/" dir-prefix container pod))) + (shell (format "*kubel - shell - %s@%s*" container pod)))) + +(defun kubel-exec-eshell-pod () + "Exec into the pod under the cursor -> eshell." + (interactive) + (kubel-setup-tramp) + (let* ((dir-prefix (or + (when (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (format "%s%s:%s@%s|" (or hop "") method user host))) "")) + (con-pod (kubel--get-container-under-cursor)) + (container (car con-pod)) + (pod (cdr con-pod)) + (default-directory (format "/%skubectl:%s@%s:/" dir-prefix container pod)) + (eshell-buffer-name (format "*kubel - eshell - %s@%s*" container pod))) + (eshell))) + +(defun kubel-exec-vterm-pod () + "Exec into the pod under the cursor -> vterm." + (require 'vterm) + (interactive) + (kubel-setup-tramp) + (let* ((dir-prefix (or + (when (tramp-tramp-file-p default-directory) + (with-parsed-tramp-file-name default-directory nil + (format "%s%s:%s@%s|" (or hop "") method user host))) "")) + (con-pod (kubel--get-container-under-cursor)) + (container (car con-pod)) + (pod (cdr con-pod)) + (default-directory (format "/%skubectl:%s@%s:/" dir-prefix container pod)) + (vterm-buffer-name (format "*kubel - vterm - %s@%s*" container pod))) + (vterm))) + +(defun kubel-exec-ansi-term-pod () + "Exec into the pod under the cursor -> `ansi-term'." + (interactive) + (let* ((con-pod (kubel--get-container-under-cursor)) + (container (car con-pod)) + (pod (cdr con-pod)) + (command (format "%s exec %s -c %s -i -t -- /usr/bin/env sh" (kubel--get-command-prefix) pod container))) + (with-current-buffer (ansi-term "bash" (concat "kubel:ansi-term:" container "@" pod)) + (process-send-string (current-buffer) (format "%s\n" command))))) + +(defun kubel-exec-pod-by-shell-command () + "Prompt shell with kubectl exec command at pod under cursor." + (interactive) + (kubel-setup-tramp) + (let* ((con-pod (kubel--get-container-under-cursor)) + (command (read-string "Shell command: " (format "%s exec %s -c %s -- " (kubel--get-command-prefix) (cdr con-pod) (car con-pod))))) + (shell-command command))) + +(defun kubel-delete-resource () + "Kubectl delete resource under cursor." + (interactive) + (dolist (pod (if (kubel--items-selected-p) + kubel--selected-items + (list (kubel--get-resource-under-cursor)))) + (let* ((process-name (format "kubel - delete %s - %s" kubel-resource pod)) + (args (list "delete" kubel-resource pod))) + (when (transient-args 'kubel-delete-popup) + (setq args (append args (list "--force" "--grace-period=0")))) + (kubel--exec process-name args)))) + +(defun kubel-jab-deployment () + "Make a trivial patch to force a new deployment. + +See https://github.com/kubernetes/kubernetes/issues/27081" + (interactive) + (dolist (deployment (if (kubel--is-deployment-view) + (if (kubel--items-selected-p) + kubel--selected-items + (list (kubel--get-resource-under-cursor))) + (list (kubel--select-resource "Deployments")))) + (let ((process-name (format "kubel - bouncing - %s" deployment))) + (kubel--exec process-name (list "patch" "deployment" deployment "-p" + (format "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"%s\"}}}}}" + (round (time-to-seconds)))))))) + +(defun kubel-scale-replicas (replicas) + "Scale resource replicas. + +REPLICAS is the number of desired replicas." + (interactive (list (read-number "Replicas: "))) + (if (kubel--is-scalable) + (let* ((resource (kubel--get-resource-under-cursor)) + (process-name (format "kubel:scale:%s/%s" kubel-resource resource))) + (kubel--exec process-name (list "scale" kubel-resource resource "--replicas" (number-to-string replicas)))) + (message + "[%s] cannot be scaled.\nOnly these resources can be scaled: [deployment, replica set, replication controller, and stateful set]." + kubel-resource))) + +(defun kubel-set-filter (filter) + "Set the pod filter. + +FILTER is the filter string." + (interactive "MFilter: ") + (setq kubel-resource-filter filter) + (kubel)) + +(defun kubel--jump-to-highlight (init search reset) + "Base function to jump to highlight. + +INIT is to be called before searching. +SEARCH is to apply the search and can be repeated safely. +RESET is to be called if the search is nil after the first attempt." + (unless (equal kubel-resource-filter "") + (funcall init) + (unless (funcall search) + (funcall reset) + (funcall search)) + (beginning-of-line))) + +(defun kubel-jump-to-next-highlight () + "Jump to the next hightlighted resrouce." + (interactive) + (kubel--jump-to-highlight + #'end-of-line + (lambda () (re-search-forward kubel-resource-filter (point-max) t)) + #'beginning-of-buffer)) + +(defun kubel-jump-to-previous-highlight () + "Jump to the previou highlighted resrouce." + (interactive) + (kubel--jump-to-highlight + #'beginning-of-line + (lambda () (re-search-backward kubel-resource-filter (point-min) t)) + #'end-of-buffer)) + +(defun kubel-rollout-history () + "See rollout history for resource under cursor." + (interactive) + (kubel--show-rollout-revision kubel-resource (kubel--get-resource-under-cursor))) + +(defun kubel-changelog () + "Opens up the changelog." + (interactive) + (browse-url "https://github.com/abrochard/kubel/blob/master/CHANGELOG.md")) + +(defun kubel-quick-edit () + "Quickly edit any resource." + (interactive) + (kubel--describe-resource + (completing-read "Select resource: " kubel-kubernetes-resources-list))) + +(defun kubel-show-process-buffer () + "Show the kubel-process-buffer." + (interactive) + (pop-to-buffer kubel--process-buffer) + (special-mode)) + +(defun kubel-mark-item () + "Mark or unmark the item under cursor." + (interactive) + (let ((item (kubel--get-resource-under-cursor))) + (unless (-contains? kubel--selected-items item) + (progn + (push item kubel--selected-items) + (forward-line 1) + (kubel))))) + +(defun kubel-unmark-item () + "Unmark the item under cursor." + (interactive) + (let ((item (kubel--get-resource-under-cursor))) + (when (-contains? kubel--selected-items item) + (progn + (setq kubel--selected-items (delete item kubel--selected-items)) + (kubel))))) + +(defun kubel-mark-all () + "Mark all items." + (interactive) + (setq kubel--selected-items '()) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (push (kubel--get-resource-under-cursor) kubel--selected-items) + (forward-line 1))) + (kubel)) + +(defun kubel-unmark-all () + "Unmark all items." + (interactive) + (setq kubel--selected-items '()) + (kubel)) + +;; popups + +(transient-define-prefix kubel-exec-popup () + "Kubel Exec Menu" + ["Actions" + ("!" "Shell command" kubel-exec-pod-by-shell-command) + ("d" "Dired" kubel-exec-pod) + ("e" "Eshell" kubel-exec-eshell-pod) + ("v" "Vterm" kubel-exec-vterm-pod) + ("a" "Ansi-term" kubel-exec-ansi-term-pod) + ("s" "Shell" kubel-exec-shell-pod)]) + +(transient-define-prefix kubel-log-popup () + "Kubel Log Menu" + ["Arguments" + ("-f" "Follow" "-f") + ("-p" "Previous" "-p") + ("-n" "Tail" "--tail=")] + ["Actions" + ("l" "Tail pod logs" kubel-get-pod-logs) + ("i" "Tail initContainer logs" kubel-get-pod-logs--initContainer) + ("L" "Tail by labels" kubel-get-logs-by-labels)]) + +(transient-define-prefix kubel-copy-popup () + "Kubel Copy Menu" + ["Actions" + ("c" "Copy resource name" kubel-copy-resource-name) + ("l" "Copy pod log command" kubel-copy-log-command) + ("p" "Copy command prefix" kubel-copy-command-prefix) + ("C" "Copy last command" kubel-copy-last-command)]) + +(transient-define-prefix kubel-delete-popup () + "Kubel Delete menu" + ["Arguments" + ("-f" "Force" "--force --grace-period=0")] + ["Actions" + ("k" "Delete resource(s)" kubel-delete-resource)]) + +(transient-define-prefix kubel-describe-popup () + "Kubel Describe Menu" + ["Arguments" + ("-y" "Yaml" "-o yaml")] + ["Actions" + ("RET" "Describe" kubel-get-resource-details)]) + +(transient-define-prefix kubel-help-popup () + "Kubel Menu" + [["Actions" + ;; global + ("RET" "Resource details" kubel-describe-popup) + ("E" "Quick edit" kubel-quick-edit) + ("g" "Refresh" kubel) + ("k" "Delete" kubel-delete-popup) + ("r" "Rollout" kubel-rollout-history)] + ["" ;; based on current view + ("p" "Port forward" kubel-port-forward-pod) + ("l" "Logs" kubel-log-popup) + ("e" "Exec" kubel-exec-popup) + ("j" "Jab" kubel-jab-deployment) + ("S" "Scale replicas" kubel-scale-replicas)] + ["Settings" + ("C" "Set context" kubel-set-context) + ("n" "Set namespace" kubel-set-namespace) + ("R" "Set resource" kubel-set-resource) + ("K" "Set kubectl config file" kubel-set-kubectl-config-file) + ("F" "Set output format" kubel-set-output-format)] + ["Filter" + ("f" "Filter" kubel-set-filter) + ("M-n" "Next highlight" kubel-jump-to-next-highlight) + ("M-p" "Previous highlight" kubel-jump-to-previous-highlight) + ("s" "Set label selector" kubel-set-label-selector)] + ["Marking" + ("m" "Mark item" kubel-mark-item) + ("u" "Unmark item" kubel-unmark-item) + ("M" "Mark all items" kubel-mark-all) + ("U" "Unmark all items" kubel-unmark-all)] + ["Utilities" + ("c" "Copy to clipboad..." kubel-copy-popup) + ("$" "Show Process buffer" kubel-show-process-buffer)]]) + +;; mode map +(defvar kubel-mode-map + (let ((map (make-sparse-keymap))) + ;; global + (define-key map (kbd "RET") 'kubel-get-resource-details) + (define-key map (kbd "K") 'kubel-set-kubectl-config-file) + (define-key map (kbd "C") 'kubel-set-context) + (define-key map (kbd "n") 'kubel-set-namespace) + (define-key map (kbd "g") 'kubel) + (define-key map (kbd "h") 'kubel-help-popup) + (define-key map (kbd "?") 'kubel-help-popup) + (define-key map (kbd "F") 'kubel-set-output-format) + (define-key map (kbd "R") 'kubel-set-resource) + (define-key map (kbd "k") 'kubel-delete-popup) + (define-key map (kbd "f") 'kubel-set-filter) + (define-key map (kbd "r") 'kubel-rollout-history) + (define-key map (kbd "E") 'kubel-quick-edit) + (define-key map (kbd "M-n") 'kubel-jump-to-next-highlight) + (define-key map (kbd "M-p") 'kubel-jump-to-previous-highlight) + (define-key map (kbd "$") 'kubel-show-process-buffer) + (define-key map (kbd "s") 'kubel-set-label-selector) + ;; based on view + (define-key map (kbd "p") 'kubel-port-forward-pod) + (define-key map (kbd "S") 'kubel-scale-replicas) + (define-key map (kbd "l") 'kubel-log-popup) + (define-key map (kbd "c") 'kubel-copy-popup) + (define-key map (kbd "e") 'kubel-exec-popup) + (define-key map (kbd "!") 'kubel-exec-pod-by-shell-command) + (define-key map (kbd "j") 'kubel-jab-deployment) + + (define-key map (kbd "m") 'kubel-mark-item) + (define-key map (kbd "u") 'kubel-unmark-item) + (define-key map (kbd "M") 'kubel-mark-all) + (define-key map (kbd "U") 'kubel-unmark-all) + + map) + "Keymap for `kubel-mode'.") + +(defvar kubel-last-position nil) + +;;;###autoload +(defun kubel (&optional directory) + "Invoke the kubel buffer. + +DIRECTORY is optional for TRAMP support." + (interactive) + (kubel--save-line) + (kubel--pop-to-buffer (kubel--buffer-name)) + (when directory (setq default-directory directory)) + (kubel-mode) + (message (concat "Namespace: " kubel-namespace))) + +(define-derived-mode kubel-mode tabulated-list-mode "Kubel" + "Special mode for kubel buffers." + (buffer-disable-undo) + (kill-all-local-variables) + (setq truncate-lines t) + (setq mode-name "Kubel") + (setq major-mode 'kubel-mode) + (use-local-map kubel-mode-map) + (let ((entries (kubel--populate-list))) + (setq tabulated-list-format (car entries)) + (setq tabulated-list-entries (cadr entries))) ; TODO handle "No resource found" + (setq tabulated-list-sort-key kubel--list-sort-key) + (setq tabulated-list-sort-key nil) + (tabulated-list-init-header) + (tabulated-list-print) + (hl-line-mode 1) + (run-mode-hooks 'kubel-mode-hook)) + +(add-hook 'kubel-mode-hook #'kubel--jump-back-to-line) + +(provide 'kubel) +;;; kubel.el ends here