--- /dev/null
+Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) <elpasignNOSPAMelpa.gnu.org> (trust undefined) created at 2024-10-22T11:05:01+0200 using EDDSA
\ No newline at end of file
--- /dev/null
+.dir-locals.el
+README.md
+*.png
--- /dev/null
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2024 Eshel Yaron
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
--- /dev/null
+#+title: Kubed NEWS -- history of user-visible changes
+#+author: Eshel Yaron
+#+email: meNOSPAMeshelyaron.com
+#+language: en
+#+options: ':t toc:nil num:nil ^:{}
+
+This file contains the release notes for Kubed, a rich Emacs interface
+for Kubernetes.
+
+For further details, see the Kubed manual:
+[[https://eshelyaron.com/sweep.html][https://eshelyaron.com/kubed.html]].
+
+* Version 0.4.2 on 2024-10-22
+
+** New user option 'kubed-list-mode-line-format'
+
+This user option lets you customize the indicator that Kubed shows in
+the mode line of resource table buffers when update is in progress.
+
+** Fix Tramp method autoload issue
+
+This version arranges for the Kubed Tramp integration to be loaded
+earlier, which circumvents the "Method ‘kubedv1’ is not known" error
+that some users have experienced.
+
+* Version 0.4.1 on 2024-09-23
+
+** New command and transient menu for scaling deployments
+
+You can now scale Kubernetes deployment with Kubed, directly with
+~kubed-scale-deployment~, from the deployments table buffer, or via
+the new transient menu ~kubed-transient-scale-deployment~.
+
+** New commands and transient menu for managing Kubernetes API proxies
+
+You can now use Kubed to create proxy servers that give access to the
+Kubernetes API server. The commands ~kubed-proxy~ and
+~kubed-stop-proxy~ start and stop proxy servers, respectively. The
+new transient menu ~kubed-transient-proxy~ lets specify options when
+starting a proxy, such as the local port to use.
+
+* Version 0.4.0 on 2024-08-23
+
+** ~kubed-list-update~ is now bound to ~g~ in resource table buffers.
+
+Previously, this command was bound to the slightly less convenient
+capital ~G~. This shadows the default binding of ~revert-buffer~,
+which you can still invoke via ~C-x x g~.
+
+** Introduce resource type specific bindings in resource table help transients.
+
+The help transient menu that you get with ~?~ in resource table
+buffers now includes type-specific bindings, such as the ~F~ key for
+forwarding a local port to a pod in the Kubed pods buffer.
+
+** New transient menu ~kubed-transient-logs~ for showing container logs.
+
+** Improved Tramp integration.
+
+Kubed now uses a bespoke Tramp method to open Dired and Shell buffers
+inside Kubernetes pods. The main advantage of this method is the
+ability to work with multiple ~kubectl~ contexts simultaneously. This
+method requires Tramp 2.7 or later, Kubed will complain if you try to
+start remote Dired or Shell with an earlier Tramp version.
+
+* Version 0.3.2 on 2024-08-15
+
+** Improved support for multiple contexts and namespaces
+
+Each buffer now has a local context and namespace, which acts as the
+default/current context and namespace when you invoke Kubed commands
+from that buffer. See new manual section "Context and Namespace" for
+more details.
+
+** New filter operators ~<~ and ~>~ in resource tables.
+
+These operators let you filter line with columns that are less than or
+greater than a given value.
+
+** New command for applying changes to displayed resource.
+
+You can now edit displayed resource YAML buffers after you select a
+resource with ~RET~ in the table buffer, and apply your changes to the
+live resource with ~C-c C-c~.
+
+* Version 0.3.1 on 2024-08-06
+
+** New command for fitting column width to content in resource tables.
+
+** New transient menu showing help in resource table buffers.
+
+* Version 0.3.0 on 2024-08-05
+
+** New command for copying name of resource at point to kill ring.
+
+** New commands for running ~kubectl~ command on resource at point.
+
+** Support bookmarking resource list and resource details buffers.
+
+** New command for jumping from resource details buffer to list buffer.
+
+** Fix installation on Doom Emacs.
+
+** New transient menu for displaying resources.
+
+** Support multiple resource list buffers with different namespaces or contexts.
+
+* Version 0.2.0 on 2024-08-02
+
+** Ensure compatibility with Emacs 29.
+
+** Add right-click context menus in resource tables.
+
+** New menu-bar "Kubernetes" menu.
+
+** New commands for restarting and watching deployments.
+
+** Add filtering capability to all resource tables.
+
+** New cron job commands: suspend, resume, and start job now.
+
+** New commands for patching resources.
+
+** New useful columns in services table and deployments table.
+
+** Use multi-column layout in transient menus.
+
+* Version 0.1.0 on 2024-07-28
+
+Initial release.
--- /dev/null
+;;; cobra.el --- Complete Cobra command lines -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <meNOSPAMeshelyaron.com>
+;; Keywords: tools
+
+;;; Commentary:
+
+;; Cobra is a popular Golang framework for CLI programs. This library
+;; defines function `cobra-read-command-line', which helps you read a
+;; command line for a program that uses Cobra, with completion.
+;; Prominent examples of Cobra programs are `kubectl' and `docker'.
+
+;;; Code:
+
+(defvar cobra--cache nil)
+
+(defun cobra-completion-table (executable s p a)
+ "Completion table for command lines that invoke EXECUTABLE.
+
+Perform completion action A on string S with predicate P."
+ (let ((start 0))
+ (while (string-match "[[:space:]=]" s start)
+ (setq start (match-end 0)))
+ (if (eq a 'metadata)
+ `(metadata
+ (category . cobra-command-line)
+ (affixation-function
+ . ,(lambda (cands)
+ (let ((max (seq-max
+ (cons 0 (mapcar #'string-width cands)))))
+ (mapcar
+ (lambda (cand)
+ (list
+ cand ""
+ (if-let
+ ((desc (get-text-property
+ 0 'cobra-argument-description
+ cand)))
+ (concat
+ (make-string (1+ (- max (string-width cand))) ?\s)
+ (propertize desc 'face 'completions-annotations))
+ "")))
+ cands)))))
+ (let* ((lines
+ (cdr
+ (if (string= s (car cobra--cache))
+ ;; Cache hit.
+ cobra--cache
+ (setq
+ cobra--cache
+ (cons s
+ (apply #'process-lines-ignore-status
+ executable "__complete"
+ (let ((args (cdr (split-string-and-unquote s))))
+ (if (string-suffix-p " " s)
+ ;; Restore omitted empty argument.
+ (nconc args '(""))
+ args))))))))
+ (code nil)
+ (comps (seq-take-while
+ (lambda (line)
+ (not (and (string-match "^:\\([0-9]+\\)$" line)
+ (setq code (string-to-number
+ (match-string 1 line))))))
+ lines)))
+ ;; `code' encodes "completion directives", as follows:
+ ;; #b000001: An error occurred, ignore completions.
+ ;; #b000010: Don't add space after completion. (TODO.)
+ ;; #b000100: Don't fall back to file completion.
+ ;; #b001000: Completions are really file extension filters.
+ ;; #b010000: Complete directory names.
+ ;; #b100000: Preserve completions order.
+ (when (and code (zerop (logand 1 code)))
+ ;; Error bit in unset, proceed.
+ (if (= #b100 (logand #b100 code))
+ ;; No file name completion.
+ (if (eq (car-safe a) 'boundaries)
+ `(boundaries
+ ,start . ,(and (string-match "[[:space:]=]" (cdr a))
+ (match-beginning 0)))
+ (let ((table
+ (mapcar
+ ;; Annotate completion candidates.
+ (lambda (comp)
+ (pcase (split-string comp "\t" t)
+ (`(,c ,d . ,_)
+ (propertize
+ c 'cobra-argument-description
+ ;; Only keep first sentence.
+ (car (split-string d "\\." t))))
+ (`(,c . ,_) c)))
+ comps)))
+ (if a (complete-with-action a table (substring s start) p)
+ ;; `try-completion'.
+ (let ((comp (complete-with-action a table (substring s start) p)))
+ (if (stringp comp) (concat (substring s 0 start) comp) comp)))))
+ ;; File name completion.
+ (setq p
+ (cond
+ ((= #b1000 (logand #b1000 code))
+ ;; `comps' are valid extensions.
+ (lambda (f)
+ (or (file-directory-p f)
+ (when (string-match "\\.[^.]*\\'" f)
+ (member (substring f (1+ (match-beginning 0)))
+ comps)))))
+ ((= #b10000 (logand #b10000 code))
+ ;; Directory name completion.
+ #'file-directory-p)))
+ (if (eq (car-safe a) 'boundaries)
+ ;; Find nested boundaries.
+ (let* ((suf (cdr a))
+ (bounds (completion-boundaries
+ (substring s start) #'completion-file-name-table p
+ (substring suf 0 (string-match "[[:space:]=]" suf)))))
+ `(boundaries ,(+ (car bounds) start) . ,(cdr bounds)))
+ (if a (complete-with-action a #'completion-file-name-table
+ (substring s start) p)
+ (let ((comp (complete-with-action a #'completion-file-name-table
+ (substring s start) p)))
+ (if (stringp comp) (concat (substring s 0 start) comp) comp))))))))))
+
+;;;###autoload
+(defun cobra-read-command-line (prompt initial &optional hist)
+ "Prompt with PROMPT for a command line starting with INITIAL.
+
+Optional argument HIST is the name of the history list variable to use,
+if it is nil or omitted, it defaults to `shell-command-history'."
+ (let ((exec (car (split-string-and-unquote (car (ensure-list initial))))))
+ (minibuffer-with-setup-hook
+ (:append (lambda ()
+ (let ((map (make-sparse-keymap)))
+ ;; Ensure SPC is self-inserting.
+ (keymap-unset map "SPC")
+ (use-local-map
+ (make-composed-keymap map (current-local-map))))))
+ (completing-read prompt (apply-partially #'cobra-completion-table exec)
+ nil nil initial (or hist 'shell-command-history)))))
+
+(provide 'cobra)
+;;; cobra.el ends here
--- /dev/null
+This is the file .../info/dir, which contains the
+topmost node of the Info hierarchy, called (dir)Top.
+The first time you invoke Info you start off looking at this node.
+\1f
+File: dir, Node: Top This is the top of the INFO tree
+
+ This (the Directory node) gives a menu of major topics.
+ Typing "q" exits, "H" lists all Info commands, "d" returns here,
+ "h" gives a primer for first-timers,
+ "mEmacs<Return>" visits the Emacs manual, etc.
+
+ In Emacs, you can click mouse button 2 on a menu item or cross reference
+ to select it.
+
+* Menu:
+
+Emacs
+* Kubed: (kubed). Kubernetes, Emacs, done!
--- /dev/null
+;;; kubed-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from cobra.el
+
+(autoload 'cobra-read-command-line "cobra" "\
+Prompt with PROMPT for a command line starting with INITIAL.
+
+Optional argument HIST is the name of the history list variable to use,
+if it is nil or omitted, it defaults to `shell-command-history'.
+
+(fn PROMPT INITIAL &optional HIST)")
+(register-definition-prefixes "cobra" '("cobra-"))
+
+\f
+;;; Generated autoloads from kubed.el
+
+(autoload 'kubed-display-resource "kubed" "\
+Display Kubernetes RESOURCE of type TYPE in CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCE. With a prefix argument \\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well.
+
+(fn TYPE RESOURCE CONTEXT &optional NAMESPACE)" t)
+(autoload 'kubed-display-resource-handle-bookmark "kubed" "\
+Display Kubernetes resource according to BOOKMARK.
+
+(fn BOOKMARK)")
+(autoload 'kubed-list-handle-bookmark "kubed" "\
+Display Kubernetes resource according to BOOKMARK.
+
+(fn BOOKMARK)")
+(autoload 'kubed-delete-resources "kubed" "\
+Delete Kubernetes RESOURCES of type TYPE in context CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCES. With a prefix argument \\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well.
+
+(fn TYPE RESOURCES CONTEXT &optional NAMESPACE)" t)
+(autoload 'kubed-edit-resource "kubed" "\
+Edit Kubernetes RESOURCE of type TYPE in context CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCES. With a prefix argument \\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well.
+
+(fn TYPE RESOURCE CONTEXT &optional NAMESPACE)" t)
+(autoload 'kubed-define-resource "kubed" "\
+Define Kubernetes RESOURCE with associated PROPERTIES and COMMANDS.
+
+RESOURCE is a symbol corresponding to a Kubernetes resource type, such
+as `pod' or `service'. This macro defines the following commands for
+interacting with Kubernetes RESOURCEs:
+
+- `kubed-display-RESOURCE': prompt for a RESOURCE and display its
+ description in YAML format. See also `kubed-yaml-setup-hook'.
+- `kubed-edit-RESOURCE': prompt for a RESOURCE and edit it.
+- `kubed-delete-RESOURCE': prompt for a RESOURCE and delete it.
+- `kubed-list-RESOURCEs': display a buffer listing all RESOURCEs in the
+ current namespace. The RESOURCEs list buffer uses a dedicated major
+ mode, `kubed-RESOURCEs-mode', which is also defined by this macro.
+- `kubed-create-RESOURCE': create a RESOURCE from a YAML or a JSON file.
+- `kubed-explain-RESOURCEs': show buffer with help about RESOURCEs.
+
+This macro also defines a prefix keymap, `kubed-RESOURCE-prefix-map',
+with bindings for the above commands.
+
+PROPERTIES is a list of lists (PROP JSON-PATH WIDTH SORT FORMAT . ATTRS)
+that specify properties of RESOURCEs. PROP is the name of the property,
+as a symbol; JSON-PATH is a JSONPath expression that evaluates to the
+value of PROP when applied to the JSON representation of a RESOURCE.
+WIDTH, SORT, FORMAT and ATTRS are optional and can be omitted. WIDTH is
+used as the default width of the column corresponding to PROP in
+RESOURCEs list buffers; SORT is sort predicate, a function that takes
+two values of PROP as strings and return non-nil if the first should
+sort before the second; FORMAT is a function that takes a value of PROP
+and formats it; and ATTRS is a plist of additional attributes of the
+PROP column, see `tabulated-list-format' for available attributes. For
+example, (phase \".status.phase\" 10) says that RESOURCE has a `phase'
+property at JSONPath \".status.phase\" whose values are typically 10
+columns wide. The first property in PROPERTIES, is used to annotate
+completion candidates when prompting for a RESOURCE.
+
+COMMANDS is a list of elements (COMMAND KEYS DOC-PREFIX . BODY) that
+define commands for RESOURCE list buffers. COMMAND is a symbol
+specifying the suffix of the command name, the full name of the command
+is `kubed-RESOURCEs-COMMAND' (for example, `kubed-pods-shell'); KEYS is
+either a string that specifies a key sequence to bind to the command in
+`kubed-RESOURCEs-mode-map', or nil if the command should not be bound;
+DOC-PREFIX is a string used to construct the docstring of the command,
+this macro appends the string \" Kubernetes RESOURCE at point.\" to it
+to obtain the final docstring; lastly, BODY is the body the command.
+Within BODY, the variable RESOURCE is let-bound to the name of the
+RESOURCE at point. For example, if RESOURCE is `pod', the following
+COMMANDS element defines a command `kubed-pods-frob' and binds it to the
+key \"f\" in `kubed-pods-mode-map':
+
+ (frob \"f\" \"Frobnicate\"
+ (message \"Preparing...\")
+ (frobnicate-pod pod)
+ (message \"Done.\"))
+
+By default, this macro assumes that RESOURCE is namespaced. To define a
+namespaceless resource type, put `:namespaced nil' before COMMANDS:
+
+ (kubed-define-resource global-thingy (PROP1 PROP2 ...) :namespaced nil
+ CMD1
+ CMD2
+ ...)
+
+Other keyword arguments that go between PROPERTIES and COMMANDS are:
+
+- `:create (ARGLIST DOCSTRING INTERACTIVE BODY...)': specialize the
+ resource creation command, `kubed-create-RESOURCE'. ARGLIST,
+ DOCSTRING, INTERACTIVE and BODY have the same meaning as in `defun'.
+- `:prefix ((KEY LABEL DEFINITION)...)': additional keybinding for the
+ prefix keymap `kubed-RESOURCE-prefix-map' and the
+ `kubed-RESOURCE-menu-map' menu. Each element (KEY LABEL DEFINITION)
+ says to bind KEY to DEFINITION in `kubed-RESOURCE-menu-map', and to
+ add DEFINITION to `kubed-RESOURCE-menu-map' with the label LABEL.
+- `:plural PLURAL': specify plural form of RESOURCE, as a symbol. If
+ you omit this keyword argument, the plural form defaults to RESOURCE
+ followed by \"s\".
+- `:logs t': generate `kubed-logs-for-RESOURCE' command.
+- `:suffixes (SUFFIX...)': add type-specific transient suffixes to
+ `kubed-list-transient'.
+
+(fn RESOURCE &optional PROPERTIES &rest COMMANDS)" nil t)
+(function-put 'kubed-define-resource 'lisp-indent-function 2)
+ (autoload 'kubed-display-pod "kubed" nil t)
+ (autoload 'kubed-edit-pod "kubed" nil t)
+ (autoload 'kubed-delete-pods "kubed" nil t)
+ (autoload 'kubed-list-pods "kubed" nil t)
+ (autoload 'kubed-create-pod "kubed" nil t)
+ (autoload 'kubed-logs-for-pod "kubed" nil t)
+ (autoload 'kubed-pod-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-namespace "kubed" nil t)
+ (autoload 'kubed-edit-namespace "kubed" nil t)
+ (autoload 'kubed-delete-namespaces "kubed" nil t)
+ (autoload 'kubed-list-namespaces "kubed" nil t)
+ (autoload 'kubed-create-namespace "kubed" nil t)
+ (autoload 'kubed-namespace-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-persistentvolume "kubed" nil t)
+ (autoload 'kubed-edit-persistentvolume "kubed" nil t)
+ (autoload 'kubed-delete-persistentvolumes "kubed" nil t)
+ (autoload 'kubed-list-persistentvolumes "kubed" nil t)
+ (autoload 'kubed-create-persistentvolume "kubed" nil t)
+ (autoload 'kubed-persistentvolume-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-service "kubed" nil t)
+ (autoload 'kubed-edit-service "kubed" nil t)
+ (autoload 'kubed-delete-services "kubed" nil t)
+ (autoload 'kubed-list-services "kubed" nil t)
+ (autoload 'kubed-create-service "kubed" nil t)
+ (autoload 'kubed-logs-for-service "kubed" nil t)
+ (autoload 'kubed-service-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-secret "kubed" nil t)
+ (autoload 'kubed-edit-secret "kubed" nil t)
+ (autoload 'kubed-delete-secrets "kubed" nil t)
+ (autoload 'kubed-list-secrets "kubed" nil t)
+ (autoload 'kubed-create-secret "kubed" nil t)
+ (autoload 'kubed-secret-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-job "kubed" nil t)
+ (autoload 'kubed-edit-job "kubed" nil t)
+ (autoload 'kubed-delete-jobs "kubed" nil t)
+ (autoload 'kubed-list-jobs "kubed" nil t)
+ (autoload 'kubed-create-job "kubed" nil t)
+ (autoload 'kubed-logs-for-job "kubed" nil t)
+ (autoload 'kubed-job-prefix-map "kubed" nil t 'keymap)
+(autoload 'kubed-watch-deployment-status "kubed" "\
+Show and update status of Kubernetes deployment DEP in a dedicate buffer.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace. CB is an optional callback function to call with
+no arguments when the deployment ends.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+
+(fn DEP &optional CONTEXT NAMESPACE CB)" t)
+(autoload 'kubed-scale-deployment "kubed" "\
+Scale deployment DEP in namespace NAMESPACE via CONTEXT to REPS replicas.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+
+(fn DEP REPS &optional CONTEXT NAMESPACE)" t)
+(autoload 'kubed-restart-deployment "kubed" "\
+Restart Kubernetes deployment DEP in namespace NAMESPACE via CONTEXT.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace. CB is an optional callback function to call with
+no arguments when the deployment ends.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+
+(fn DEP &optional CONTEXT NAMESPACE)" t)
+ (autoload 'kubed-display-deployment "kubed" nil t)
+ (autoload 'kubed-edit-deployment "kubed" nil t)
+ (autoload 'kubed-delete-deployments "kubed" nil t)
+ (autoload 'kubed-list-deployments "kubed" nil t)
+ (autoload 'kubed-create-deployment "kubed" nil t)
+ (autoload 'kubed-logs-for-deployment "kubed" nil t)
+ (autoload 'kubed-deployment-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-replicaset "kubed" nil t)
+ (autoload 'kubed-edit-replicaset "kubed" nil t)
+ (autoload 'kubed-delete-replicasets "kubed" nil t)
+ (autoload 'kubed-list-replicasets "kubed" nil t)
+ (autoload 'kubed-create-replicaset "kubed" nil t)
+ (autoload 'kubed-logs-for-replicaset "kubed" nil t)
+ (autoload 'kubed-replicaset-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-statefulset "kubed" nil t)
+ (autoload 'kubed-edit-statefulset "kubed" nil t)
+ (autoload 'kubed-delete-statefulsets "kubed" nil t)
+ (autoload 'kubed-list-statefulsets "kubed" nil t)
+ (autoload 'kubed-create-statefulset "kubed" nil t)
+ (autoload 'kubed-logs-for-statefulset "kubed" nil t)
+ (autoload 'kubed-statefulset-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-cronjob "kubed" nil t)
+ (autoload 'kubed-edit-cronjob "kubed" nil t)
+ (autoload 'kubed-delete-cronjobs "kubed" nil t)
+ (autoload 'kubed-list-cronjobs "kubed" nil t)
+ (autoload 'kubed-create-cronjob "kubed" nil t)
+ (autoload 'kubed-cronjob-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-ingressclass "kubed" nil t)
+ (autoload 'kubed-edit-ingressclass "kubed" nil t)
+ (autoload 'kubed-delete-ingressclasses "kubed" nil t)
+ (autoload 'kubed-list-ingressclasss "kubed" nil t)
+ (autoload 'kubed-create-ingressclass "kubed" nil t)
+ (autoload 'kubed-ingressclass-prefix-map "kubed" nil t 'keymap)
+ (autoload 'kubed-display-ingress "kubed" nil t)
+ (autoload 'kubed-edit-ingress "kubed" nil t)
+ (autoload 'kubed-delete-ingresses "kubed" nil t)
+ (autoload 'kubed-list-ingresss "kubed" nil t)
+ (autoload 'kubed-create-ingress "kubed" nil t)
+ (autoload 'kubed-ingress-prefix-map "kubed" nil t 'keymap)
+(autoload 'kubed-use-context "kubed" "\
+Set current Kubernetes context to CONTEXT.
+
+(fn CONTEXT)" t)
+(autoload 'kubed-rename-context "kubed" "\
+Rename Kubernetes context OLD to NEW.
+
+(fn OLD NEW)" t)
+(autoload 'kubed-display-config "kubed" "\
+Display current Kubernetes client settings in a YAML buffer." t)
+(autoload 'kubed-set-namespace "kubed" "\
+Set default Kubernetes namespace in CONTEXT to NAMESPACE.
+
+Interactively, prompt for NAMESPACE and use the current context. With a
+prefix argument, prompt for CONTEXT as well.
+
+(fn NAMESPACE &optional CONTEXT)" t)
+(autoload 'kubed-apply "kubed" "\
+Apply CONFIG to Kubernetes resource of kind KIND via CONTEXT.
+
+(fn CONFIG &optional KIND CONTEXT)" t)
+(autoload 'kubed-create "kubed" "\
+Create resource of kind KIND with definition DEFINITION via CONTEXT.
+
+(fn DEFINITION &optional KIND CONTEXT)" t)
+(autoload 'kubed-run "kubed" "\
+Run container IMAGE in a new Kubernetes POD.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context. NAMESPACE is the namespace in which to create POD,
+defaulting to the current namespace. PORT is the port to expose,
+defaulting to none. If ATTACH is non-nil, then attach to the created
+image with a `comint-mode' buffer, and pop to that buffer. Non-nil
+STDIN says to keep the standard input of the container open; non-nil TTY
+says to allocate a TTY for the container; and non-nil RM says to remove
+the container after it exits. ENVS is a list of strings \"VAR=VAL\"
+which specify environment variables VAR and values VAL to give them in
+the created container. ARGS are command line arguments for the
+container command. If COMMAND is non-nil, ARGS consist of a complete
+command line, that overrides the container command instead of just
+providing it with arguments.
+
+Interactively, prompt for POD and IMAGE. With a prefix argument, prompt
+for NAMESPACE; with a double prefix argument, also prompt for CONTEXT.
+
+(fn POD IMAGE &optional CONTEXT NAMESPACE PORT ATTACH STDIN TTY RM ENVS COMMAND ARGS)" t)
+(autoload 'kubed-logs "kubed" "\
+Show logs for container CONTAINER in Kubernetes RESOURCE of type TYPE.
+
+Optional argument CONTEXT is the `kubectl' context to use; NAMESPACE is
+the namespace for RESOURCE; CONTAINER is the name of the container in
+RESOURCE to show logs for, or t which specifies all relevant containers.
+
+With a prefix argument, prompt for NAMESPACE; with a double prefix
+argument, also prompt for CONTEXT.
+
+(fn TYPE RESOURCE &optional CONTEXT NAMESPACE CONTAINER FOLLOW LIMIT PREFIX SINCE TAIL TIMESTAMPS)" t)
+(autoload 'kubed-forward-port-to-pod "kubed" "\
+Forward LOCAL-PORT to REMOTE-PORT of Kubernetes pod POD.
+
+(fn POD LOCAL-PORT REMOTE-PORT CONTEXT NAMESPACE)" t)
+(autoload 'kubed-attach "kubed" "\
+Attach to running process in CONTAINER in Kubernetes POD.
+
+Optional argument NAMESPACE is the namespace in which to look for POD.
+Non-nil STDIN says to connect local standard input to remote process.
+Non-nil TTY says to use a TTY for standard input.
+
+Interactively, prompt for POD; if there are multiple pod containers,
+prompt for CONTAINER as well. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+STDIN and TTY are t interactively.
+
+(fn POD &optional CONTAINER CONTEXT NAMESPACE STDIN TTY)" t)
+(autoload 'kubed-diff "kubed" "\
+Display difference between Kubernetes resource DEFINITION and current state.
+
+DEFINITION is either a file name or a buffer. Interactively, prompt for
+a YAML or JSON file name to use as DEFINITION.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; non-nil INCLUDE-MANAGED (interactively, the prefix
+argument) says to include managed fields in the comparison.
+
+(fn DEFINITION &optional INCLUDE-MANAGED CONTEXT)" t)
+(autoload 'kubed-exec "kubed" "\
+Execute COMMAND with ARGS in CONTAINER in Kubernetes POD.
+
+Optional argument NAMESPACE is the namespace in which to look for POD.
+Non-nil STDIN says to connect local standard input to remote process.
+Non-nil TTY says to use a TTY for standard input.
+
+Interactively, prompt for POD; if there are multiple pod containers,
+prompt for CONTAINER as well. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+STDIN and TTY are t interactively.
+
+(fn POD COMMAND &optional CONTAINER CONTEXT NAMESPACE STDIN TTY ARGS)" t)
+(autoload 'kubed-patch "kubed" "\
+Patch Kubernetes resource NAME of TYPE with patch PATCH.
+
+Optional argument NAMESPACE is the namespace in which to look for NAME.
+STRATEGY is the patch type to use, one of \"json\", \"merge\" and
+\"strategic\", defaulting to \"strategic\".
+
+Interactively, prompt for TYPE, NAME and PATCH. With a prefix argument,
+prompt for NAMESPACE too. With a double prefix argument, also prompt
+for CONTEXT.
+
+(fn TYPE NAME PATCH &optional CONTEXT NAMESPACE STRATEGY)" t)
+(autoload 'kubed-proxy "kubed" "\
+Start a web server on localhost proxying the Kubernetes API server.
+
+ADDR and PORT are the local address and port to bind the proxy server
+to. They default to 127.0.0.1 and 8001, respectively. API-PREFIX is a
+prefix under which to proxy the Kubernetes API. WWW-DIR is a local
+directory to serve under WWW-PREFIX, which defaults to \"/static/\".
+Lastly, CONTEXT is the the `kubectl' context to use, defaulting to the
+local context.
+
+(fn &optional ADDR PORT API-PREFIX WWW-DIR WWW-PREFIX CONTEXT)" t)
+(autoload 'kubed-stop-proxy "kubed" "\
+Stop local proxy for context CONTEXT, defaulting to the local context.
+
+(fn &optional CONTEXT)" t)
+(autoload 'kubed-explain "kubed" "\
+Show help buffer with explanation about Kubernetes resource FIELD.
+
+(fn FIELD)" t)
+(autoload 'kubed-kubectl-command "kubed" "\
+Execute `kubectl' COMMAND.
+
+This function calls `shell-command' (which see) to do the work.
+
+Interactively, prompt for COMMAND with completion for `kubectl' arguments.
+
+(fn COMMAND)" t)
+ (autoload 'kubed-prefix-map "kubed" nil t 'keymap)
+(defvar kubed-menu-bar-mode nil "\
+Non-nil if Kubed-Menu-Bar mode is enabled.
+See the `kubed-menu-bar-mode' command
+for a description of this minor mode.
+Setting this variable directly does not take effect;
+either customize it (see the info node `Easy Customization')
+or call the function `kubed-menu-bar-mode'.")
+(custom-autoload 'kubed-menu-bar-mode "kubed" nil)
+(autoload 'kubed-menu-bar-mode "kubed" "\
+Add \"Kubernetes\" menu to your menu bar.
+
+This is a global minor mode. If called interactively, toggle the
+`Kubed-Menu-Bar mode' mode. If the prefix argument is positive, enable
+the mode, and if it is zero or negative, disable the mode.
+
+If called from Lisp, toggle the mode if ARG is `toggle'. Enable the
+mode if ARG is nil, omitted, or is a positive number. Disable the mode
+if ARG is a negative number.
+
+To check whether the minor mode is enabled in the current buffer,
+evaluate `(default-value \\='kubed-menu-bar-mode)'.
+
+The mode's hook is called both when the mode is enabled and when it is
+disabled.
+
+(fn &optional ARG)" t)
+(register-definition-prefixes "kubed" '("kubed-"))
+
+\f
+;;; Generated autoloads from kubed-common.el
+
+(register-definition-prefixes "kubed-common" '("kubed-"))
+
+\f
+;;; Generated autoloads from kubed-tramp.el
+
+(autoload 'kubed-tramp-context "kubed-tramp" "\
+Extract `kubectl' context from Kubed Tramp remote file name FILE-NAME.
+
+(fn FILE-NAME)")
+(autoload 'kubed-tramp-namespace "kubed-tramp" "\
+Extract Kubernetes namespace from Kubed Tramp remote file name FILE-NAME.
+
+(fn FILE-NAME)")
+(autoload 'kubed-tramp-assert-support "kubed-tramp" "\
+Check if Kubed Tramp support is available, throw `user-error' if not.")
+(autoload 'kubed-tramp-enable "kubed-tramp" "\
+Enable Kubed integration with Tramp.")
+ (with-eval-after-load 'tramp (kubed-tramp-enable))
+(register-definition-prefixes "kubed-tramp" '("kubed-tramp--"))
+
+\f
+;;; Generated autoloads from kubed-transient.el
+
+ (autoload 'kubed-transient "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-pod "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-deployment "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-service "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-job "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-replicaset "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs-for-statefulset "kubed-transient" nil t)
+ (autoload 'kubed-transient-logs "kubed-transient" nil t)
+ (autoload 'kubed-transient-scale-deployment "kubed-transient" nil t)
+ (autoload 'kubed-transient-proxy "kubed-transient" nil t)
+ (autoload 'kubed-transient-rollout "kubed-transient" nil t)
+ (autoload 'kubed-transient-attach "kubed-transient" nil t)
+ (autoload 'kubed-transient-diff "kubed-transient" nil t)
+ (autoload 'kubed-transient-exec "kubed-transient" nil t)
+ (autoload 'kubed-transient-run "kubed-transient" nil t)
+ (autoload 'kubed-transient-apply "kubed-transient" nil t)
+ (autoload 'kubed-transient-patch "kubed-transient" nil t)
+ (autoload 'kubed-transient-create "kubed-transient" nil t)
+ (autoload 'kubed-transient-display "kubed-transient" nil t)
+ (autoload 'kubed-transient-edit "kubed-transient" nil t)
+ (autoload 'kubed-transient-delete "kubed-transient" nil t)
+ (autoload 'kubed-transient-create-cronjob "kubed-transient" nil t)
+ (autoload 'kubed-transient-create-ingress "kubed-transient" nil t)
+ (autoload 'kubed-transient-create-deployment "kubed-transient" nil t)
+ (autoload 'kubed-transient-create-job "kubed-transient" nil t)
+ (autoload 'kubed-list-transient "kubed-transient" nil t)
+(register-definition-prefixes "kubed-transient" '("kubed-transient-"))
+\f
+;;; End of scraped data
+
+(provide 'kubed-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; kubed-autoloads.el ends here
--- /dev/null
+;;; kubed-common.el --- Common definitons for Kubed -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <meNOSPAMeshelyaron.com>
+;; Keywords: tools
+
+;;; Commentary:
+
+;; This library provides definitions that both `kubed' and `kubed-tramp'
+;; require.
+
+;;; Code:
+
+(defvar kubed-tramp-method "kubedv1" ;Versioned, for compatibility.
+ ;; (find-file "/kubedv1:CONTEXT%NAMESPACE%POD%CONTAINER:/some/file")
+ "Name of the Kubed Tramp method.")
+
+(defcustom kubed-kubectl-program "kubectl"
+ "Name of `kubectl' executable to use for interacting with Kubernetes."
+ :type 'string
+ :group 'kubed)
+
+(provide 'kubed-common)
+;;; kubed-tramp.el ends here
--- /dev/null
+;; Generated package description from kubed.el -*- no-byte-compile: t -*-
+(define-package "kubed" "0.4.2" "Kubernetes, Emacs, done!" '((emacs "29.1")) :commit "d0a0f2407575b321457f48582d04ef69471fe561" :authors '(("Eshel Yaron" . "meNOSPAMeshelyaron.com")) :maintainer '("Eshel Yaron" . "~eshel/kubed-develNOSPAMlists.sr.ht") :keywords '("tools" "kubernetes" "containers") :url "https://eshelyaron.com/kubed.html")
--- /dev/null
+;;; kubed-tramp.el --- Kubed Tramp integration -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <meNOSPAMeshelyaron.com>
+;; Keywords: tools
+
+;;; Commentary:
+
+;; This library provides Tramp integration for Kubed. This is similar
+;; to the built-in "kubernetes" Tramp method from tramp-container.el,
+;; except that the Kubed method always requires the container name and
+;; Kubernetes namespace to be specified, as well a `kubectl' context.
+;; In other words, the filename syntax of this method is fully explicit.
+;; An explicit syntax is useful when juggling between different contexts
+;; and namespaces with Kubed; on the other hand, for finding a remote
+;; file with C-x C-f, the concise syntax of the built-in "kubernetes"
+;; method is probably more convenient.
+
+;;; Code:
+
+(require 'kubed-common)
+(require 'tramp)
+
+(defun kubed-tramp--context (vec)
+ "Extract the context name from a kubernetes host name in VEC."
+ (or (when-let ((host (and vec (tramp-file-name-host vec))))
+ (nth 0 (split-string host "%")))
+ ""))
+
+(defun kubed-tramp--namespace (vec)
+ "Extract the namespace from a kubernetes host name in VEC."
+ (or (when-let ((host (and vec (tramp-file-name-host vec))))
+ (nth 1 (split-string host "%")))
+ ""))
+
+(defun kubed-tramp--pod (vec)
+ "Extract the pod name from a kubernetes host name in VEC."
+ (or (when-let ((host (and vec (tramp-file-name-host vec))))
+ (nth 2 (split-string host "%")))
+ ""))
+
+(defun kubed-tramp--container (vec)
+ "Extract the container name from a kubernetes host name in VEC."
+ (or (when-let ((host (and vec (tramp-file-name-host vec))))
+ (nth 3 (split-string host "%")))
+ ""))
+
+;;;###autoload
+(defun kubed-tramp-context (file-name)
+ "Extract `kubectl' context from Kubed Tramp remote file name FILE-NAME."
+ (nth 0 (split-string
+ (tramp-file-name-host (tramp-dissect-file-name file-name)) "%")))
+
+;;;###autoload
+(defun kubed-tramp-namespace (file-name)
+ "Extract Kubernetes namespace from Kubed Tramp remote file name FILE-NAME."
+ (nth 1 (split-string
+ (tramp-file-name-host (tramp-dissect-file-name file-name)) "%")))
+
+;;;###autoload
+(defun kubed-tramp-assert-support ()
+ "Check if Kubed Tramp support is available, throw `user-error' if not."
+ (unless (assoc kubed-tramp-method tramp-methods)
+ (user-error "Kubed Tramp support requires Tramp version 2.7 or later")))
+
+;;;###autoload
+(defun kubed-tramp-enable ()
+ "Enable Kubed integration with Tramp."
+ (when (boundp 'tramp-extra-expand-args) ; Tramp 2.7+
+
+ (setf (alist-get kubed-tramp-method tramp-methods nil nil #'string=)
+ `((tramp-login-program ,kubed-kubectl-program)
+ (tramp-login-args (("exec")
+ ("--context" "%x")
+ ("--namespace" "%y")
+ ("-c" "%a")
+ ("%h")
+ ("-it")
+ ("--")
+ ("%l")))
+ (tramp-direct-async (,tramp-default-remote-shell "-c"))
+ (tramp-remote-shell ,tramp-default-remote-shell)
+ (tramp-remote-shell-login ("-l"))
+ (tramp-remote-shell-args ("-i" "-c"))))
+
+ (connection-local-set-profile-variables
+ 'kubed-tramp-connection-local-default-profile
+ '((tramp-extra-expand-args
+ ?a (kubed-tramp--container (car tramp-current-connection))
+ ?h (kubed-tramp--pod (car tramp-current-connection))
+ ?x (kubed-tramp--context (car tramp-current-connection))
+ ?y (kubed-tramp--namespace (car tramp-current-connection)))))
+
+ (connection-local-set-profiles
+ `(:application tramp :protocol ,kubed-tramp-method)
+ 'kubed-tramp-connection-local-default-profile)))
+
+;;;###autoload (with-eval-after-load 'tramp (kubed-tramp-enable))
+
+(provide 'kubed-tramp)
+;;; kubed-tramp.el ends here
--- /dev/null
+;;; kubed-transient.el --- Kubernetes transient menus -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <meNOSPAMeshelyaron.com>
+;; Keywords: tools
+
+;;; Commentary:
+
+;; This library extends Kubed with transient menus for various
+;; Kubernetes operations.
+
+;;; Code:
+
+(require 'kubed)
+(require 'transient)
+
+(defun kubed-transient-read-date (prompt default _history)
+ "Prompt with PROMPT for a date, defaulting to DEFAULT.
+
+Return an RFC3339 string representation of the selected date."
+ (require 'org)
+ (when (fboundp 'org-read-date)
+ (format-time-string "%Y-%m-%dT%H:%M:%S%:z"
+ (org-read-date 'with-time t nil prompt default))))
+
+(defun kubed-transient-read-context (prompt _initial-input _history)
+ "Prompt with PROMPT for Kubernetes context."
+ (kubed-read-context prompt (kubed-local-context)))
+
+(defun kubed-transient-read-namespace (prompt _initial-input _history)
+ "Prompt with PROMPT for Kubernetes namespace."
+ (let ((context (or (seq-some (lambda (s)
+ (and (cl-typep s 'transient-infix)
+ (equal (oref s argument) "--context=")
+ (oref s value)))
+ transient--suffixes)
+ (kubed-local-context))))
+ (kubed-read-namespace prompt (kubed--namespace context) nil context)))
+
+(defun kubed-transient-read-ingressclass (prompt _initial-input _history)
+ "Prompt with PROMPT for Kubernetes ingress class."
+ (kubed-read-ingressclass prompt))
+
+(defun kubed-transient-read-service-and-port (prompt _initial-input _history)
+ "Prompt with PROMPT for Kubernetes service and port number."
+ (let ((service (kubed-read-service prompt)))
+ (concat service ":" (number-to-string (read-number "Port number: ")))))
+
+(defun kubed-transient-read-resource-definition-file-name
+ (_prompt _initial-input _history)
+ "Read and return Kubernetes resource definition file name."
+ (kubed-read-resource-definition-file-name))
+
+;;;###autoload (autoload 'kubed-transient "kubed-transient" nil t)
+(transient-define-prefix kubed-transient ()
+ "Perform Kubernetes operation."
+ ["Kubernetes"
+ [ :pad-keys t
+ ("RET" "Display" kubed-transient-display)
+ ("+" "Create" kubed-transient-create)
+ ("*" "Apply" kubed-transient-apply)
+ ("D" "Delete" kubed-transient-delete)]
+ [("e" "Edit" kubed-transient-edit)
+ ("r" "Run" kubed-transient-run)
+ ("a" "Attach" kubed-transient-attach)
+ ("X" "Exec" kubed-transient-exec)]
+ [("l" "Logs" kubed-transient-logs)
+ ("d" "Diff" kubed-transient-diff)
+ ("P" "Patch" kubed-transient-patch)
+ ("R" "Rollout" kubed-transient-rollout)]
+ [("$" "Scale" kubed-transient-scale-deployment)
+ (":" "Proxy" kubed-transient-proxy)
+ ("E" "Explain" kubed-explain)
+ ("!" "Command line" kubed-kubectl-command)]])
+
+(defmacro kubed-transient-logs-for-resource (resource &optional plural)
+ "Define transient menu for showing logs for Kubernetes RESOURCE.
+
+Optional argument PLURAL is the plural form of RESOURCE. If nil, it
+defaults to \"RESOURCEs\"."
+ (let ((name (intern (concat "kubed-transient-logs-for-" resource))))
+ `(transient-define-prefix ,name (&optional value)
+ ,(format "Show logs for a Kubernetes %s." resource)
+ [,(format "Kubernetes Logs for %s\n" (capitalize resource))
+ ["Actions"
+ ("l" "Show Logs" ,(intern (concat "kubed-logs-for-" resource)))
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-b" "Limit bytes" "--limit-bytes="
+ :prompt "Byte limit: " :reader transient-read-number-N+)
+ ("-t" "Limit lines" "--tail="
+ :prompt "Byte limit: " :reader transient-read-number-N+)
+ ("-S" "Since time" "--since-time="
+ :prompt "Since time: " :reader kubed-transient-read-date)]
+ ["Switches"
+ ("-A" "All containers" "--all-containers")
+ ("-f" "Stream logs" "--follow")
+ ("-P" "Add pod and container" "--prefix")
+ ("-T" "Add timestamps" "--timestamps")]]
+ (interactive
+ (list (kubed-transient-args 'kubed-transient-logs)))
+ (transient-setup ',name nil nil
+ :value value
+ :scope '("logs" ,(concat (or plural
+ (concat resource "s"))
+ "/"))))))
+
+;;;###autoload (autoload 'kubed-transient-logs-for-pod "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "pod")
+
+;;;###autoload (autoload 'kubed-transient-logs-for-deployment "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "deployment")
+
+;;;###autoload (autoload 'kubed-transient-logs-for-service "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "service")
+
+;;;###autoload (autoload 'kubed-transient-logs-for-job "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "job")
+
+;;;###autoload (autoload 'kubed-transient-logs-for-replicaset "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "replicaset")
+
+;;;###autoload (autoload 'kubed-transient-logs-for-statefulset "kubed-transient" nil t)
+(kubed-transient-logs-for-resource "statefulset")
+
+;;;###autoload (autoload 'kubed-transient-logs "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-logs ()
+ "Show logs for containers running in Kubernetes."
+ ["Kubernetes Logs\n"
+ ["Kinds"
+ ("p" "Pod" kubed-transient-logs-for-pod)
+ ("d" "Deployment" kubed-transient-logs-for-deployment)
+ ("j" "Job" kubed-transient-logs-for-job)
+ ("s" "Service" kubed-transient-logs-for-service)
+ ("l" "Any type" kubed-logs)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-b" "Limit bytes" "--limit-bytes="
+ :prompt "Byte limit: " :reader transient-read-number-N+)
+ ("-t" "Limit lines" "--tail="
+ :prompt "Byte limit: " :reader transient-read-number-N+)
+ ("-S" "Since time" "--since-time="
+ :prompt "Since time: " :reader kubed-transient-read-date)]
+ ["Switches"
+ ("-A" "All containers" "--all-containers")
+ ("-f" "Stream logs" "--follow")
+ ("-P" "Add pod and container" "--prefix")
+ ("-T" "Add timestamps" "--timestamps")]]
+ (interactive)
+ (transient-setup 'kubed-transient-logs nil nil
+ :scope '("logs")))
+
+;;;###autoload (autoload 'kubed-transient-scale-deployment "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-scale-deployment ()
+ "Scale Kubernetes deployments."
+ ["Kubernetes Scale Deployment\n"
+ ["Action"
+ ("$" "Scale" kubed-scale-deployment)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-r" "Replicas" "--replicas="
+ :prompt "Number of replicas: " :reader transient-read-number-N+)]]
+ (interactive)
+ (transient-setup 'kubed-transient-scale-deployment nil nil
+ :scope '("scale" "deployment")))
+
+;;;###autoload (autoload 'kubed-transient-proxy "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-proxy ()
+ "Create a proxy between localhost and the Kubernetes API server."
+ ["Kubernetes Proxy\n"
+ ["Action"
+ (":" "Start proxy" kubed-proxy)
+ ("|" "Stop proxy" kubed-stop-proxy)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-a" "Address" "--address="
+ :prompt "Local proxy address: ")
+ ("-p" "Port" "--port="
+ :prompt "Local proxy port: " :reader transient-read-number-N0)
+ ("-w" "WWW directory" "--www="
+ :prompt "Local WWW directory: " :reader transient-read-existing-directory)
+ ("-P" "WWW prefix" "--www-prefix="
+ :prompt "WWW prefix: ")
+ ("-A" "API prefix" "--api-prefix="
+ :prompt "API prefix: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-proxy nil nil
+ :scope '("proxy")))
+
+;;;###autoload (autoload 'kubed-transient-rollout "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-rollout ()
+ "Manage Kubernetes deployments."
+ ["Kubernetes Rollout\n"
+ ["Actions"
+ ("W" "Watch" kubed-watch-deployment-status)
+ ("R" "Restart" kubed-restart-deployment)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-rollout nil nil
+ :scope '("rollout")))
+
+;;;###autoload (autoload 'kubed-transient-attach "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-attach ()
+ "Attach to running process in container in Kubernetes pod."
+ ["Kubernetes Attach\n"
+ ["Actions"
+ ("a" "Attach" kubed-attach)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Switches"
+ ("-i" "Open stdin" "--stdin")
+ ("-t" "Allocate TTY" "--tty")]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-attach nil nil
+ :value '("--stdin" "--tty")
+ :scope '("attach")))
+
+;;;###autoload (autoload 'kubed-transient-diff "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-diff ()
+ "Display difference between Kubernetes resource definition and current state."
+ ["Kubernetes Diff\n"
+ ["Actions"
+ ("d" "Diff" kubed-diff)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Switches"
+ ("-M" "Include managed fields" "--show-managed-fields")]
+ ["Options"
+ ("-f" "Definition file" "--filename="
+ :reader kubed-transient-read-resource-definition-file-name)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-diff nil nil
+ :scope '("diff")))
+
+;;;###autoload (autoload 'kubed-transient-exec "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-exec ()
+ "Execute command in Kubernetes pod."
+ ["Kubernetes Exec\n"
+ ["Actions"
+ ("X" "Execute" kubed-exec)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Switches"
+ ("-i" "Open stdin" "--stdin")
+ ("-t" "Allocate TTY" "--tty")]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("--" "Command" "-- ="
+ :prompt "Command: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-exec nil nil
+ :value '("--stdin" "--tty")
+ :scope '("exec")))
+
+;;;###autoload (autoload 'kubed-transient-run "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-run ()
+ "Run container image in a Kubernetes pod."
+ ["Kubernetes Run\n"
+ ["Actions"
+ ("r" "Run" kubed-run)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Switches"
+ ("-A" "Attach" "--attach")
+ ("-i" "Open stdin" "--stdin")
+ ("-t" "Allocate TTY" "--tty")
+ ("-R" "Remove after exit" "--rm")
+ ("-c" "Override container command" "--command")]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-I" "Image" "--image="
+ :prompt "Image to deploy: ")
+ ("-p" "Port" "--port="
+ :prompt "Port to expose: " :reader transient-read-number-N+)
+ ("-E" "Env vars" "--env="
+ :prompt "Set environment VAR=VAL: "
+ :multi-value repeat)
+ ("--" "Arguments" "-- ="
+ :prompt "Arguments for container command: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-run nil nil
+ :scope '("run")))
+
+;;;###autoload (autoload 'kubed-transient-apply "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-apply ()
+ "Apply configuration to Kubernetes resource."
+ ["Kubernetes Apply\n"
+ ["Actions"
+ ("*" "Apply" kubed-apply)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-f" "Definition file" "--filename="
+ :reader kubed-transient-read-resource-definition-file-name)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-apply nil nil
+ :scope '("apply")))
+
+;;;###autoload (autoload 'kubed-transient-patch "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-patch ()
+ "Apply patch to Kubernetes resource."
+ ["Kubernetes Patch\n"
+ ["Actions"
+ ("P" "Patch" kubed-patch)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-t" "Patch type" "--type="
+ :prompt "Patch type: "
+ :choices ("strategic" "merge" "json"))]]
+ (interactive)
+ (transient-setup 'kubed-transient-patch nil nil
+ :scope '("patch")))
+
+;;;###autoload (autoload 'kubed-transient-create "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-create ()
+ "Create Kubernetes resource."
+ ["Kubernetes Create\n"
+ ["Kinds"
+ ("p" "Pod" kubed-create-pod)
+ ("d" "Deployment" kubed-transient-create-deployment)
+ ("j" "Job" kubed-transient-create-job)
+ ("c" "CronJob" kubed-transient-create-cronjob)
+ ("s" "Service" kubed-create-service)]
+ ["More"
+ :pad-keys t
+ ("S" "Secret" kubed-create-secret)
+ ("N" "Namespace" kubed-create-namespace)
+ ("i" "Ingress" kubed-transient-create-ingress)
+ ("+" "Any type" kubed-create)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-f" "Definition file" "--filename="
+ :reader kubed-transient-read-resource-definition-file-name)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-create nil nil
+ :scope '("create")))
+
+;;;###autoload (autoload 'kubed-transient-display "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-display ()
+ "Display Kubernetes resource."
+ ["Kubernetes Display\n"
+ ["Kinds"
+ ("p" "Pod" kubed-display-pod)
+ ("d" "Deployment" kubed-display-deployment)
+ ("j" "Job" kubed-display-job)
+ ("c" "CronJob" kubed-display-cronjob)
+ ("s" "Service" kubed-display-service)]
+ ["More"
+ :pad-keys t
+ ("S" "Secret" kubed-display-secret)
+ ("N" "Namespace" kubed-display-namespace)
+ ("i" "Ingress" kubed-display-ingress)
+ ("RET" "Any type" kubed-display-resource)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-display nil nil
+ :scope '("get")))
+
+;;;###autoload (autoload 'kubed-transient-edit "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-edit ()
+ "Edit Kubernetes resource."
+ ["Kubernetes Edit\n"
+ ["Kinds"
+ ("p" "Pod" kubed-edit-pod)
+ ("d" "Deployment" kubed-edit-deployment)
+ ("j" "Job" kubed-edit-job)
+ ("c" "CronJob" kubed-edit-cronjob)
+ ("s" "Service" kubed-edit-service)]
+ ["More"
+ :pad-keys t
+ ("S" "Secret" kubed-edit-secret)
+ ("N" "Namespace" kubed-edit-namespace)
+ ("i" "Ingress" kubed-edit-ingress)
+ ("RET" "Any type" kubed-edit-resource)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-edit nil nil
+ :scope '("edit")))
+
+;;;###autoload (autoload 'kubed-transient-delete "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-delete ()
+ "Delete Kubernetes resource."
+ ["Kubernetes Delete\n"
+ ["Kinds"
+ ("p" "Pod" kubed-delete-pods)
+ ("d" "Deployment" kubed-delete-deployments)
+ ("j" "Job" kubed-delete-jobs)
+ ("c" "CronJob" kubed-delete-cronjobs)
+ ("s" "Service" kubed-delete-services)]
+ ["More"
+ :pad-keys t
+ ("S" "Secret" kubed-delete-secrets)
+ ("N" "Namespace" kubed-delete-namespaces)
+ ("i" "Ingress" kubed-delete-ingresses)
+ ("D" "Any type" kubed-delete-resources)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)]]
+ (interactive)
+ (transient-setup 'kubed-transient-delete nil nil
+ :scope '("delete")))
+
+;;;###autoload (autoload 'kubed-transient-create-cronjob "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-create-cronjob ()
+ "Create Kubernetes cronjob."
+ ["Kubernetes Create CronJob\n"
+ ["Actions"
+ ("+" "Create" kubed-create-cronjob)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-I" "Image" "--image="
+ :prompt "Image to run: ")
+ ("-S" "Schedule" "--schedule="
+ :prompt "Cron schedule: ")
+ ("--" "Command" "-- ="
+ :prompt "Command: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-create-cronjob nil nil
+ :scope '("create" "cronjob")))
+
+;;;###autoload (autoload 'kubed-transient-create-ingress "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-create-ingress ()
+ "Create Kubernetes ingress."
+ ["Kubernetes Create Ingress\n"
+ ["Actions"
+ ("+" "Create" kubed-create-ingress)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-c" "Class" "--class="
+ :prompt "Class" :reader kubed-transient-read-ingressclass)
+ ("-d" "Default backend service" "--default-backend="
+ :prompt "Default backend service"
+ :reader kubed-transient-read-service-and-port)
+ ("-a" "Annotation" "--annotation="
+ :prompt "Ingress annotations: "
+ :multi-value repeat)
+ ("-r" "Rule" "--rule="
+ :prompt "Ingress rule: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-create-ingress nil nil
+ :scope '("create" "ingress")))
+
+;;;###autoload (autoload 'kubed-transient-create-deployment "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-create-deployment ()
+ "Create Kubernetes deployment."
+ ["Kubernetes Create Deployment\n"
+ ["Actions"
+ ("+" "Create" kubed-create-deployment)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-r" "Replicas" "--replicas="
+ :prompt "Number of replicas: " :reader transient-read-number-N+)
+ ("-I" "Image" "--image="
+ :prompt "Images to deploy: "
+ :multi-value repeat)
+ ("-p" "Port" "--port="
+ :prompt "Port to expose: " :reader transient-read-number-N+)
+ ("--" "Command" "-- ="
+ :prompt "Command: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-create-deployment nil nil
+ :scope '("create" "deployment")))
+
+;;;###autoload (autoload 'kubed-transient-create-job "kubed-transient" nil t)
+(transient-define-prefix kubed-transient-create-job ()
+ "Create Kubernetes job."
+ ["Kubernetes Create Job\n"
+ ["Actions"
+ ("+" "Create" kubed-create-job)
+ ("c" "Create from cronjob" kubed-create-job-from-cronjob)
+ ("!" "Command line" kubed-kubectl-command)]
+ ["Options"
+ ("-n" "Namespace" "--namespace="
+ :prompt "Namespace" :reader kubed-transient-read-namespace)
+ ("-C" "Context" "--context="
+ :prompt "Context" :reader kubed-transient-read-context)
+ ("-I" "Image" "--image="
+ :prompt "Image to run: ")
+ ("--" "Command" "-- ="
+ :prompt "Command: ")]]
+ (interactive)
+ (transient-setup 'kubed-transient-create-job nil nil
+ :scope '("create" "job")))
+
+;;;###autoload (autoload 'kubed-list-transient "kubed-transient" nil t)
+(transient-define-prefix kubed-list-transient ()
+ "Help for Kubernetes resource list buffers."
+ ;; TODO: Add a type-specific group with `:setup-children'.
+ ["Kubernetes Resources:"
+ ["Select"
+ :pad-keys t
+ ("RET" "Select" kubed-list-select-resource)
+ ("C-o" "Display" kubed-list-display-resource :transient t)
+ ("e" "Edit" kubed-list-edit :transient t)
+ ("w" "Copy name" kubed-list-copy-as-kill :transient t)
+ ("!" "Command line" kubed-list-kubectl-command :transient t)]
+ ["Delete"
+ ("D" "Delete" kubed-list-delete :transient t)
+ ("d" "Mark" kubed-list-mark-for-deletion :transient t)
+ ("u" "Unmark" kubed-list-unmark :transient t)
+ ("x" "Delete marked" kubed-list-delete-marked :transient t)
+ ("+" "Create" kubed-list-create :transient t)]
+ ["Table"
+ ("|" "Fit column" kubed-list-fit-column-width-to-content :transient t)
+ ("}" "Widen column" tabulated-list-widen-current-column :transient t)
+ ("{" "Narrow column" tabulated-list-narrow-current-column :transient t)
+ ("S" "Sort" tabulated-list-sort :transient t)
+ ("/" "Filter" kubed-list-set-filter :transient t)]
+ ["Movement"
+ :pad-keys t
+ ("n" "Next line" next-line :transient t)
+ ("p" "Previous line" previous-line :transient t)
+ ("TAB" "Next column" kubed-list-next-column :transient t)
+ ("S-TAB" "Previous column" kubed-list-previous-column :transient t)
+ ("g" "Update" kubed-list-update :transient t)]]
+ ["Type Specific"
+ :class transient-columns
+ :setup-children
+ (lambda (_)
+ (transient-parse-suffixes 'kubed-list-transient
+ kubed-list-transient-extra-suffixes))])
+
+(provide 'kubed-transient)
+;;; kubed-transient.el ends here
--- /dev/null
+;;; kubed.el --- Kubernetes, Emacs, done! -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Eshel Yaron <meNOSPAMeshelyaron.com>
+;; Maintainer: Eshel Yaron <~eshel/kubed-develNOSPAMlists.sr.ht>
+;; Keywords: tools kubernetes containers
+;; URL: https://eshelyaron.com/kubed.html
+;; Package-Version: 0.4.2
+;; Package-Requires: ((emacs "29.1"))
+
+;;; Commentary:
+
+;; This library defines commands for interacting with Kubernetes
+;; resources, such as Kubernetes pods, services, deployments, and more.
+;;
+;; Use `kubed-display-pod' to display a Kubernetes pod,
+;; `kubed-edit-pod' to edit it, `kubed-delete-pods' to delete it, and
+;; `kubed-list-pods' to see a menu of all pods. You can create new pods
+;; from YAML or JSON files with `kubed-create-pod'.
+;;
+;; Similar commands are defined for other types of resources as well.
+;;
+;; This library interacts with Kubernetes via `kubectl', and uses the
+;; current `kubectl' context and namespace by default. To change your
+;; current context or namespace, use commands `kubed-use-context' and
+;; `kubed-set-namespace' respectively; you can also interact with
+;; resources in other namespaces without changing your default
+;; namespace, for example you can call `kubed-list-pods' with a prefix
+;; argument to choose another namespace for listing pods. The prefix
+;; keymap `kubed-prefix-map' gives you quick access to these and other
+;; useful commands, you may want to bind it globally to a convenient key
+;; with `keymap-global-set':
+;;
+;; (keymap-global-set "C-c k" 'kubed-prefix-map)
+;;
+;; In addition, the command `kubed-transient' lets you explore various
+;; Kubernetes operations with a transient menu interface. You may also
+;; want to enable `kubed-menu-bar-mode', which add a "Kubernetes" menu
+;; to your menu-bar with many useful entries.
+;;
+;; If you want to work with more or different types of Kubernetes
+;; resources, use the macro `kubed-define-resource'. This macro defines
+;; some common functions and commands that'll get you started with ease.
+;;
+;; For more information, see the Kubed manual at (info "(kubed)Top"), or
+;; online at https://eshelyaron.com/kubed.html
+
+;;; Code:
+
+(require 'kubed-common)
+
+(defgroup kubed nil
+ "Kubernetes interface."
+ :group 'tools)
+
+(defcustom kubed-default-context-and-namespace nil
+ "Default `kubectl' context and Kubernetes namespace.
+
+This is either a cons cell (CONTEXT . NAMESPACE), or nil. If nil, Kubed
+initializes this option to a non-nil value next time Kubed consults it."
+ :type '(choice (const :tag "Initialize from `kubectl' on first use" nil)
+ (cons :tag "Context and namespace"
+ (string :tag "Context")
+ (string :tag "Namespace"))))
+
+(defcustom kubed-yaml-setup-hook '(yaml-ts-mode view-mode)
+ "List of functions to call in Kubernetes resource description YAML buffers.
+
+The first function in the list should normally be the major mode to use,
+by default it is `yaml-ts-mode'."
+ :type 'hook)
+
+(defcustom kubed-logs-setup-hook '(view-mode)
+ "List of functions to call when setting up Kubernetes pod logs buffers."
+ :type 'hook)
+
+(defcustom kubed-name-column '("Name" 48 t)
+ "Specification of resource name column in Kubernetes resource list buffers."
+ :type '(list string natnum boolean))
+
+(defvar kubed--data nil)
+
+(defun kubed--alist (type context namespace)
+ "Return information about resources in CONTEXT of type TYPE in NAMESPACE."
+ (alist-get namespace
+ (alist-get type
+ (alist-get context
+ kubed--data nil nil #'string=)
+ nil nil #'string=)
+ nil nil #'equal))
+
+(gv-define-setter kubed--alist (value type context namespace)
+ `(setf (alist-get ,namespace
+ (alist-get ,type
+ (alist-get ,context
+ kubed--data nil nil #'string=)
+ nil nil #'string=)
+ nil nil #'equal)
+ ,value))
+
+(defvar kubed--columns nil)
+
+(defun kubed-update (type context &optional namespace)
+ "Update list of resources of type TYPE in CONTEXT and NAMESPACE."
+ (when (process-live-p (alist-get 'process (kubed--alist type context namespace)))
+ (user-error "Update in progress"))
+ (let* ((out (get-buffer-create (format " *kubed-get-%s*" type)))
+ (err (get-buffer-create (format " *kubed-get-%s-stderr*" type)))
+ (columns (alist-get type kubed--columns nil nil #'string=)))
+ (with-current-buffer out (erase-buffer))
+ (setf (alist-get 'process (kubed--alist type context namespace))
+ (make-process
+ :name (format "*kubed-get-%s*" type)
+ :buffer out
+ :stderr err
+ :command (append
+ (list kubed-kubectl-program "get" type
+ "--context" context
+ (format "--output=custom-columns=%s"
+ (mapconcat #'car columns ",")))
+ (when namespace (list "--namespace" namespace)))
+ :sentinel
+ (lambda (_proc status)
+ (cond
+ ((string= status "finished\n")
+ (let (new offsets eol)
+ (with-current-buffer out
+ (goto-char (point-min))
+ (setq eol (pos-eol))
+ (while (re-search-forward "[^ ]+" eol t)
+ (push (1- (match-beginning 0)) offsets))
+ (setq offsets (nreverse offsets))
+ (forward-char 1)
+ (while (not (eobp))
+ (let ((cols nil)
+ (beg (car offsets))
+ (ends (append (cdr offsets)
+ (list (- (pos-eol) (point))))))
+ (dolist (column columns)
+ (let ((str (string-trim (buffer-substring
+ (+ (point) beg)
+ (+ (point) (car ends))))))
+ (push (if-let ((f (cdr column))) (funcall f str) str)
+ cols)
+ (setq beg (pop ends))))
+ (push (nreverse cols) new))
+ (forward-line 1)))
+ (setf (kubed--alist type context namespace)
+ (list (cons 'resources
+ (mapcar (lambda (c) (list (car c) (apply #'vector c)))
+ new))))
+ (let ((bufs nil))
+ (dolist (buf (buffer-list))
+ (and (equal (buffer-local-value 'kubed-list-type buf) type)
+ (equal (buffer-local-value 'kubed-list-context buf) context)
+ (equal (buffer-local-value 'kubed-list-namespace buf) namespace)
+ (with-current-buffer buf
+ (when (derived-mode-p 'kubed-list-mode)
+ (revert-buffer)
+ (when-let ((win (get-buffer-window)))
+ (set-window-point win (point))
+ (push buf bufs))))))
+ (walk-windows
+ (lambda (win)
+ (let ((buf (window-buffer win)))
+ (when (memq buf bufs)
+ (set-window-point
+ win (with-current-buffer buf (point))))))))
+ (message (format "Updated Kubernetes %S." type))))
+ ((string= status "exited abnormally with code 1\n")
+ (with-current-buffer err
+ (goto-char (point-max))
+ (insert "\n" status))
+ (display-buffer err))))))))
+
+(defvar-local kubed-display-resource-info nil
+ "Information about Kubernetes resource that current buffer displays.
+
+The value is a list (TYPE NAME CONTEXT NAMESPACE), where TYPE is the
+type of the resource, NAME is the name of the resource, CONTEXT is the
+`kubectl' context to use for accessing the resource, and NAMESPACE is
+the namespace of the resource, or nil if TYPE is not namespaced.")
+
+(put 'kubed-display-resource-info 'permanent-local t)
+
+(defun kubed-display-resource-revert (&optional _ _)
+ "Clear and populate current Kubernetes resource buffer."
+ (seq-let (type name context namespace)
+ kubed-display-resource-info
+ (let ((inhibit-read-only t)
+ (target (current-buffer)))
+ (buffer-disable-undo)
+ (with-temp-buffer
+ (unless (zerop
+ (apply
+ #'call-process
+ kubed-kubectl-program nil t nil "get"
+ type "--output=yaml" name
+ (append (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
+ (error "Failed to display Kubernetes resource `%s'" name))
+ (let ((source (current-buffer)))
+ (with-current-buffer target
+ (replace-buffer-contents source)
+ (set-buffer-modified-p nil)
+ (buffer-enable-undo)))))))
+
+(defun kubed-display-resource-in-buffer
+ (buffer type resource &optional context namespace)
+ "Display Kubernetes RESOURCE of type TYPE in BUFFER."
+ (let ((info (list type resource context namespace)))
+ (with-current-buffer (get-buffer-create buffer)
+ (setq-local kubed-display-resource-info info)
+ (kubed-display-resource-revert)
+ (goto-char (point-min))
+ (run-hooks 'kubed-yaml-setup-hook)
+ (kubed-display-resource-mode)
+ (current-buffer))))
+
+(defun kubed-display-resource-short-description
+ (type resource context namespace)
+ "Return short string to use as a label for RESOURCE of type TYPE."
+ (concat type "/" resource
+ (when namespace (concat "@" namespace))
+ (when context (concat "[" context "]"))))
+
+(defun kubed-namespaced-p (type &optional context)
+ "Return non-nil if TYPE is a namespaced resource type in context CONTEXT."
+ (member type (kubed-api-resources context t)))
+
+(defun kubed--namespace (context &optional prompt-p)
+ "Return Kubernetes namespace for CONTEXT.
+
+Non-nil optional argument PROMPT-P says to prompt for a namespace."
+ (let ((ns (if (equal context (kubed-local-context))
+ (kubed-local-namespace)
+ (kubed-current-namespace context))))
+ ;; If CONTEXT is another context that has no configured namespace,
+ ;; then NS is nil and we must prompt.
+ (unless ns (setq prompt-p t))
+ (if prompt-p
+ (kubed-read-namespace "Namespace" ns nil context)
+ ns)))
+
+;;;###autoload
+(defun kubed-display-resource
+ (type resource context &optional namespace)
+ "Display Kubernetes RESOURCE of type TYPE in CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCE. With a prefix argument \
+\\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \
+\\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well."
+ (interactive
+ (let ((type nil) (context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-display))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless type
+ (setq type (kubed-read-resource-type "Type of resource to display"
+ nil context)))
+ (when (and (null namespace) (kubed-namespaced-p type context))
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list type (kubed-read-resource-name type "Display" nil nil
+ context namespace)
+ context namespace)))
+ (display-buffer
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ type resource context namespace)
+ "*")
+ type resource context namespace)))
+
+(declare-function bookmark-prop-get "bookmark")
+(declare-function bookmark-get-front-context-string "bookmark")
+(declare-function bookmark-get-rear-context-string "bookmark")
+(declare-function bookmark-make-record-default "bookmark")
+
+;;;###autoload
+(defun kubed-display-resource-handle-bookmark (bookmark)
+ "Display Kubernetes resource according to BOOKMARK."
+ (require 'bookmark)
+ (seq-let (type name context namespace)
+ (bookmark-prop-get bookmark 'resource)
+ (set-buffer
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ type name context namespace)
+ "*")
+ type name context namespace))
+ (when-let ((str (bookmark-get-front-context-string bookmark))
+ ((search-forward str (point-max) t)))
+ (goto-char (match-beginning 0)))
+ (when-let ((str (bookmark-get-rear-context-string bookmark))
+ ((search-backward str (point-min) t)))
+ (goto-char (match-end 0)))))
+
+(put 'kubed-display-resource-handle-bookmark 'bookmark-handler-type "KubedResource")
+
+(defun kubed-display-resource-make-bookmark ()
+ "Return bookmark pointing to currently displayed Kubernetes resource."
+ (require 'bookmark)
+ (seq-let (type name context namespace) kubed-display-resource-info
+ (cons
+ (kubed-display-resource-short-description type name context namespace)
+ (append
+ (list
+ (cons 'handler #'kubed-display-resource-handle-bookmark)
+ (cons 'resource kubed-display-resource-info))
+ (bookmark-make-record-default t)))))
+
+(defun kubed-display-resource-jump-to-list ()
+ "Jump to line in resources list that corresponds to the displayed resource."
+ (interactive)
+ (seq-let (type name context namespace) kubed-display-resource-info
+ (apply (intern (concat "kubed-list-" type)) context
+ (when namespace (list namespace)))
+ (kubed-list-go-to-line name)))
+
+(defun kubed-display-resource-diff ()
+ "Show diff of current buffer with current state of the displayed resource."
+ (interactive)
+ (kubed-diff (current-buffer) nil (nth 2 kubed-display-resource-info)))
+
+(defun kubed-display-resource-replace ()
+ "Replace displayed Kubernetes resource with current buffer contents."
+ (interactive)
+ (let (choice)
+ (while (= ?d (setq choice
+ (car (read-multiple-choice
+ "Replace resource with changes in current buffer?"
+ '((?y "yes") (?n "no") (?d "diff"))))))
+ (kubed-display-resource-diff))
+ (when (= ?y choice)
+ (let ((err-buf (get-buffer-create " *kubed-replace*")))
+ (with-current-buffer err-buf (erase-buffer))
+ (unless (zerop (call-process-region nil nil kubed-kubectl-program
+ nil err-buf nil "replace" "-f" "-"))
+ (display-buffer err-buf)
+ (user-error "`kubectl replace' failed!")))
+ (message "Replaced Kubernetes resource with changes in current buffer.")
+ (revert-buffer))))
+
+(defun kubed-display-resource-p (_symbol buffer)
+ "Return non-nil if `kubed-display-resource-mode' is enabled in BUFFER.
+
+The first argument, SYMBOL, is ignored. You can use this function as
+the `completion-predicate' property of commands that you define that
+should only be available in buffers that display Kubernetes resources."
+ (buffer-local-value 'kubed-display-resource-mode buffer))
+
+(dolist (cmd '(kubed-display-resource-jump-to-list
+ kubed-display-resource-diff
+ kubed-display-resource-replace))
+ (put cmd 'completion-predicate #'kubed-display-resource-p))
+
+(defvar-keymap kubed-display-resource-mode-map
+ :doc "Keymap buffers that display Kubernetes resource."
+ "C-c C-j" #'kubed-display-resource-jump-to-list
+ "C-c C-=" #'kubed-display-resource-diff
+ "C-c C-c" #'kubed-display-resource-replace)
+
+(define-minor-mode kubed-display-resource-mode
+ "Minor mode for buffers that display a Kubernetes resource."
+ :interactive nil
+ :lighter " Kubed"
+ (when kubed-display-resource-mode
+ (setq-local
+ revert-buffer-function #'kubed-display-resource-revert
+ bookmark-make-record-function #'kubed-display-resource-make-bookmark)))
+
+(defun kubed-list-filter-lt-operator (v s)
+ "Return non-nil if S is less than V as a number or as a string."
+ (let ((l (string-to-number s)) (r (string-to-number v)))
+ (if (= l r) (string< s v) (< l r))))
+
+(defun kubed-list-filter-gt-operator (v s)
+ "Return non-nil if S is greater than V as a number or as a string."
+ (let ((l (string-to-number s)) (r (string-to-number v)))
+ (if (= l r) (string> s v) (> l r))))
+
+(defcustom kubed-list-filter-operator-alist
+ '((= . string=)
+ (~ . string-match-p)
+ (< . kubed-list-filter-lt-operator)
+ (> . kubed-list-filter-gt-operator))
+ "Association list of filter operators and functions that implement them.
+
+Elements of the list are cons cells (OP . FN), where OP is a symbol that
+is used as a filter operator and FN is a function that implements OP.
+FN takes two arguments, a string STR and a parameter VAL. FN should
+return non-nil if STR and VAL are related according to OP: to determine
+if a line in which column COL is STR satisfies the filter (OP COL VAL),
+Kubed checks if the form (FN VAL STR) evaluates to non-nil."
+ :type '(alist :key-type (symbol :tag "Operator") :value-type function))
+
+(defun kubed-list-interpret-atomic-filter (atom)
+ "Return function that implements atomic filter ATOM."
+ (if (eq (car-safe atom) 'quote)
+ (let ((p (kubed-list-interpret-atomic-filter (cadr atom))))
+ (lambda (x) (not (funcall p x))))
+ (let* ((column-number (tabulated-list--column-number (symbol-name (nth 1 atom))))
+ (value (nth 2 atom))
+ (value (if (stringp value) value (prin1-to-string value)))
+ (op (alist-get (car atom) kubed-list-filter-operator-alist)))
+ (unless op (user-error "Unknown filter operator `%S'" (car atom)))
+ (lambda (x) (funcall op value (aref (cadr x) column-number))))))
+
+(defvar-local kubed-list-filter nil "Filter in effect in the current buffer.")
+
+(defun kubed-list-interpret-filter (&optional filter)
+ "Return function that implements FILTER.
+
+If FILTER is omitted or nil, it defaults to `kubed-list-filter'."
+ (let ((conjunction (or filter kubed-list-filter)))
+ (if (listp (car conjunction))
+ (let ((conjuncts
+ (mapcar (lambda (disjunction)
+ (if (listp (car disjunction))
+ (let ((disjuncts
+ (mapcar #'kubed-list-interpret-atomic-filter
+ disjunction)))
+ (lambda (entry)
+ (catch 'keep-it
+ (dolist (pred disjuncts)
+ (when (funcall pred entry)
+ (throw 'keep-it t)))
+ nil)))
+ ;; Single atomic disjunct.
+ (kubed-list-interpret-atomic-filter disjunction)))
+ (or conjunction kubed-list-filter))))
+ (lambda (entry)
+ (catch 'keep-it
+ (dolist (pred conjuncts)
+ (unless (funcall pred entry)
+ (throw 'keep-it nil)))
+ t)))
+ ;; Single atomic conjunct.
+ (kubed-list-interpret-atomic-filter conjunction))))
+
+(defun kubed-list-validate-atomic-filter (atom)
+ "Return string explaining why ATOM is invalid, or nil if it is valid."
+ (unless (consp atom)
+ (throw 'validation-error
+ (format (substitute-quotes
+ "Invalid atomic filter `%S', must be a list")
+ atom)))
+ (if (eq (car-safe atom) 'quote)
+ (kubed-list-validate-atomic-filter (cadr atom))
+ (unless (length= atom 3)
+ (throw 'validation-error
+ (format (substitute-quotes
+ "Invalid atomic filter `%S', must have three elements")
+ atom)))
+ (unless (assq (car atom) kubed-list-filter-operator-alist)
+ (throw 'validation-error
+ (format (substitute-quotes
+ "No operator `%S' in `kubed-list-filter-operator-alist'")
+ (car atom))))
+ (unless (ignore-errors
+ (tabulated-list--column-number (symbol-name (nth 1 atom))))
+ (throw 'validation-error (format (substitute-quotes
+ "Invalid column name `%S'")
+ (nth 1 atom))))))
+
+(defun kubed-list-validate-filter (filter)
+ "Return string explaining why FILTER is invalid, or nil if it is valid."
+ (catch 'validation-error
+ (if (listp (car-safe filter))
+ (dolist (disjunction filter)
+ (if (and (consp disjunction) (listp (car disjunction)))
+ (dolist (disjunct disjunction)
+ (kubed-list-validate-atomic-filter disjunct))
+ (kubed-list-validate-atomic-filter disjunction)))
+ (kubed-list-validate-atomic-filter filter))))
+
+(defvar-local kubed-list-filter-history-variable nil
+ "History list variable to use for filter history in the current buffer.")
+
+(defvar-local kubed--list-read-filter-target-buffer nil
+ "Resource list buffer for which this minibuffer is reading a filter.")
+
+(defvar-local kubed-list-type nil)
+(defvar-local kubed-list-context nil)
+(defvar-local kubed-list-namespace nil)
+
+(defvar-local kubed-list-transient-extra-suffixes nil
+ "List of transient suffixes for the type of resources in current buffer.")
+
+(defun kubed-list-go-to-line (id)
+ "Go to beginning of table line with ID."
+ (let ((pos nil))
+ (save-excursion
+ ;; Wait for refresh to finish, if currently underway.
+ (while (process-live-p
+ (alist-get 'process (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (accept-process-output
+ (alist-get 'process (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace))
+ 1))
+ (goto-char (point-min))
+ (while (not (or pos (eobp)))
+ (if (equal id (tabulated-list-get-id))
+ (setq pos (point))
+ (forward-line))))
+ (goto-char pos)))
+
+(defun kubed-list-try-read-filter ()
+ "Try to read a resource list filter in the minibuffer.
+
+Exit the minibuffer if successful, else report the error and move point
+to the location of the error. If point is not already at the location
+of the error, push a mark before moving point."
+ (interactive "" minibuffer-mode)
+ (let* ((prompt-end (minibuffer-prompt-end))
+ (contents (minibuffer-contents))
+ (error-point nil) (error-message nil) (form nil) (inval nil))
+ (with-temp-buffer
+ (condition-case err
+ (progn
+ ;; FIXME: There is a small edge case here that could get
+ ;; better treatment: when `contents' ends with " ?", it
+ ;; espaces the terminating closing parenthesis and leads us
+ ;; to incorrectly report the input as incomplete.
+ (insert "(" contents ")")
+ (goto-char (point-min))
+ (setq form (read (current-buffer))))
+ (error (setq error-point (+ prompt-end (1- (point)))
+ error-message err))))
+ (cond
+ (error-point
+ (unless (= (point) error-point) (push-mark))
+ (goto-char error-point)
+ (minibuffer-message (error-message-string error-message)))
+ ((setq inval (with-current-buffer kubed--list-read-filter-target-buffer
+ (kubed-list-validate-filter form)))
+ (minibuffer-message inval))
+ (t (exit-minibuffer)))))
+
+(defvar-keymap kubed-list-read-filter-map
+ :parent minibuffer-local-map
+ "TAB" #'completion-at-point
+ "M-?" #'completion-help-at-point
+ "<remap> <exit-minibuffer>" #'kubed-list-try-read-filter)
+
+(defun kubed-list-read-filter (prompt)
+ "Prompt with PROMPT for a filter for the current buffer."
+ (let* ((buf (current-buffer))
+ (cols (seq-map #'car tabulated-list-format))
+ (vals (let ((tmp nil))
+ (dolist (ent (alist-get 'resources
+ (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (let ((i 0))
+ (dolist (col cols)
+ (push (aref (cadr ent) i)
+ (alist-get col tmp nil nil #'string=))
+ (setq i (1+ i)))))
+ (mapcar #'delete-dups tmp)))
+ (filter
+ (minibuffer-with-setup-hook
+ (lambda ()
+ (set-syntax-table emacs-lisp-mode-syntax-table)
+ (setq-local kubed--list-read-filter-target-buffer buf)
+ (add-hook
+ 'completion-at-point-functions
+ (lambda ()
+ (let ((cont (buffer-substring
+ (minibuffer-prompt-end) (point)))
+ (bounds (or (bounds-of-thing-at-point 'symbol)
+ (cons (point) (point)))))
+ (with-temp-buffer
+ (set-syntax-table emacs-lisp-mode-syntax-table)
+ (insert "(" cont)
+ (when-let ((fn-argi (elisp--fnsym-in-current-sexp))
+ (argi (cadr fn-argi)))
+ (if (= argi 0)
+ ;; Complete operators.
+ (list
+ (car bounds) (cdr bounds)
+ (mapcar #'car kubed-list-filter-operator-alist))
+ (when (car fn-argi)
+ (cond
+ ((= argi 1)
+ ;; Complete column names.
+ (list (car bounds) (cdr bounds) cols))
+ ((= argi 2)
+ ;; Complete column values.
+ (when-let ((beg (nth 1 (syntax-ppss)))
+ ;; Grab preceding symbol.
+ (col (save-excursion
+ (goto-char beg)
+ (forward-char 1)
+ (forward-sexp 2)
+ (thing-at-point 'symbol))))
+ (list (car bounds) (cdr bounds)
+ (alist-get col vals
+ nil nil #'string=)))))))))))
+ nil t))
+ (read-from-minibuffer
+ (format-prompt prompt "disable")
+ (mapconcat #'prin1-to-string kubed-list-filter " ")
+ kubed-list-read-filter-map nil
+ kubed-list-filter-history-variable ""))))
+ (car (ignore-errors (read-from-string (format "(%s)" filter))))))
+
+(defun kubed-list-set-filter (filter)
+ "Set the filter of the current buffer to FILTER.
+
+FILTER determines which resources to keep. FILTER can be an atomic
+filter, which is a list (OP COL VAL), where OP is an operator defined in
+`kubed-list-filter-operator-alist' (which see), COL is a symbol whose
+name is a column name, and VAL is a string or an object whose printed
+representation is compared to the value of the column COL according to
+OP. For example, the atomic filter (= Name foobar) keeps only resources
+whose name is \"foobar\". (= Name \"foobar\") does exactly the same.
+To negate an atomic filter, quote it. E.g. use \\='(~ Namespace kube)
+to hide all resources in namespaces whose name contains \"kube\".
+
+FILTER can also be a list of sub-filters (SUB1 SUB2 ...) where each
+sub-filter is either an atomic filter or a list of atomic filters. If a
+sub-filter is a list of atomic filters, then that sub-filter denotes the
+disjunction of those atomic filters. FILTER denotes the conjunction of
+the sub-filters. In particular, FILTER nil denotes the empty
+conjunction which is always true (keeps all resources).
+
+More examples:
+
+- `((= Name foobar) (~ Namespace kube))': keep only resources named
+ \"foobar\" in namespaces that contain \"kube\".
+- `(((= Name foobar) (~ Namespace kube)))': keep resources that are
+ either named \"foobar\" or in a namespace that contains \"kube\".
+- `(((= Name foobar) (~ Namespace kube)) \\='(~ Starttime 2024-07))':
+ keep only resources that are either named \"foobar\" or in a namespace
+ that contains \"kube\", and were not started during July 2024.
+
+Interactively, prompt for FILTER sans the outermost set of parenthesis.
+For example, enter \"= Name foobar\" in the minibuffer to specify the
+atomic FILTER (= Name foobar).
+
+See also Info node \"(kubed) List Filter\"."
+ (interactive (list (kubed-list-read-filter "Set filter")) kubed-list-mode)
+ (when-let ((validation-error (kubed-list-validate-filter filter)))
+ (user-error validation-error))
+ (setq-local kubed-list-filter filter)
+ (revert-buffer))
+
+(defun kubed-list-mark-for-deletion ()
+ "Mark Kubernetes resource at point for deletion."
+ (interactive "" kubed-list-mode)
+ (tabulated-list-put-tag (propertize "D" 'help-echo "Marked for deletion") t))
+
+(defun kubed-list-unmark ()
+ "Remove mark from Kubernetes resource at point."
+ (interactive "" kubed-list-mode)
+ (tabulated-list-put-tag " " t))
+
+(defun kubed-list-previous-column (&optional n)
+ "Move backward N columns.
+
+Interactively, N is the numeric value of the prefix argument, defaulting
+to 1."
+ (interactive "p" kubed-list-mode)
+ (kubed-list-next-column (- n)))
+
+(defun kubed-list-next-column (&optional n)
+ "Move forward N columns.
+
+Interactively, N is the numeric value of the prefix argument, defaulting
+to 1."
+ (interactive "p" kubed-list-mode)
+ (let ((next (point))
+ (times (abs (or n 1)))
+ (dir-fn (if (< 0 n)
+ #'next-single-property-change
+ #'previous-single-property-change)))
+ (dotimes (_ times)
+ (setq next (funcall dir-fn next 'tabulated-list-column-name))
+ (when (= (char-after next) ?\n)
+ ;; At line boundary, go to first/last column of next line.
+ (setq next (funcall dir-fn next 'tabulated-list-column-name)))
+ (unless next (user-error "End of table")))
+ (goto-char next)))
+
+(defun kubed-list-copy-as-kill (click)
+ "Copy name of Kubernetes resource at CLICK into the kill ring."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((ent (tabulated-list-get-entry (mouse-set-point click)))
+ (new (aref ent 0)))
+ (progn
+ (kill-new new)
+ (message "Copied resource name `%s'" new))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-context-menu (menu click)
+ "Extend MENU with common actions on Kubernetes resource at CLICK."
+ (when (tabulated-list-get-entry (posn-point (event-start click)))
+ (define-key menu [kubed-list-select-resource]
+ '(menu-item "Select" kubed-list-select-resource))
+ (define-key menu [kubed-list-display-resource]
+ '(menu-item "Display" kubed-list-display-resource))
+ (define-key menu [kubed-list-delete]
+ '(menu-item "Delete" kubed-list-delete))
+ (define-key menu [kubed-list-patch]
+ '(menu-item "Patch" kubed-list-patch))
+ (define-key menu [kubed-list-edit]
+ '(menu-item "Edit" kubed-list-edit))
+ (define-key menu [kubed-list-kubectl-command]
+ '(menu-item "Execute `kubectl' command" kubed-list-kubectl-command))
+ (define-key menu [kubed-list-copy-as-kill]
+ '(menu-item "Copy name" kubed-list-copy-as-kill)))
+ menu)
+
+(defun kubed-list-update (&optional quiet)
+ "Update list of Kubernetes resources.
+
+If optional argument QUIET is non-nil, do not emit a message when
+starting to update. Display a message when the update is done
+regardless of QUIET."
+ (interactive "" kubed-list-mode)
+ (kubed-update kubed-list-type kubed-list-context kubed-list-namespace)
+ (force-mode-line-update)
+ (unless quiet (minibuffer-message (format "Updating Kubernetes %S..." kubed-list-type))))
+
+(defun kubed-list-delete-marked ()
+ "Delete marked Kubernetes resources."
+ (interactive "" kubed-list-mode)
+ (let (delete-list)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (eq (char-after) ?D)
+ (push (tabulated-list-get-id) delete-list))
+ (forward-line)))
+ (if delete-list
+ (when (y-or-n-p (format "Delete %d marked Kubernetes resources?"
+ (length delete-list)))
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (member (tabulated-list-get-id) delete-list)
+ (tabulated-list-put-tag
+ (propertize "K" 'help-echo "Deletion in progress")))
+ (forward-line)))
+ (let ((errb (generate-new-buffer " *kubed-list-delete-marked-stderr*"))
+ (buf (current-buffer)))
+ (make-process
+ :name "*kubed-list-delete-marked*"
+ :stderr errb
+ :command (append
+ (list kubed-kubectl-program "delete" kubed-list-type)
+ delete-list)
+ :sentinel (lambda (_proc status)
+ (cond
+ ((string= status "finished\n")
+ (message (format "Deleted %d marked Kubernetes resources."
+ (length delete-list)))
+ (when (buffer-live-p buf)
+ (with-current-buffer buf
+ (kubed-list-update t))))
+ ((string= status "exited abnormally with code 1\n")
+ (with-current-buffer errb
+ (goto-char (point-max))
+ (insert "\n" status))
+ (display-buffer errb)))))))
+ (user-error "No Kubernetes resources marked for deletion"))))
+
+(defun kubed-list-display-resource (click)
+ "Display Kubernetes resource at CLICK in another window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-display-resource
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-select-resource (click)
+ "Display Kubernetes resource at CLICK in current window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (switch-to-buffer
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ "*")
+ kubed-list-type resource kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-select-resource-other-window (click)
+ "Display Kubernetes resource at CLICK in other window and select that window."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (switch-to-buffer-other-window
+ (kubed-display-resource-in-buffer
+ (concat "*Kubed "
+ (kubed-display-resource-short-description
+ kubed-list-type resource kubed-list-context kubed-list-namespace)
+ "*")
+ kubed-list-type resource kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-delete (click)
+ "Delete Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (when (y-or-n-p (format "Delete `%s'?" resource))
+ (kubed-delete-resources kubed-list-type (list resource)
+ kubed-list-context kubed-list-namespace))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-patch (click)
+ "Patch Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-patch kubed-list-type resource
+ (kubed-read-patch) kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-edit (click)
+ "Edit Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-edit-resource kubed-list-type resource
+ kubed-list-context kubed-list-namespace)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-kubectl-command (click)
+ "Use Kubernetes resource at CLICK as argument for `kubectl' command."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-kubectl-command
+ (kubed-read-kubectl-command
+ "Execute command: "
+ (cons (concat
+ " "
+ (when kubed-list-namespace (concat "-n " kubed-list-namespace " "))
+ (when kubed-list-context (concat "--context " kubed-list-context " "))
+ kubed-list-type " " resource)
+ ;; Put point after "kubectl ".
+ 0)))
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-logs (click)
+ "Show logs for Kubernetes resource at CLICK."
+ (interactive (list last-nonmenu-event) kubed-list-mode)
+ (if-let ((resource (tabulated-list-get-id (mouse-set-point click))))
+ (kubed-logs kubed-list-type resource kubed-list-context kubed-list-namespace
+ t t nil t)
+ (user-error "No Kubernetes resource at point")))
+
+(defun kubed-list-create (definition &optional kind)
+ "Create Kubernetes resource of kind KIND from definition file DEFINITION."
+ (interactive (list (kubed-read-resource-definition-file-name)) kubed-list-mode)
+ (kubed-create definition kind kubed-list-context)
+ (kubed-list-update t))
+
+(defun kubed-list-column-number-at-point ()
+ "Return table column number at point."
+ (let ((start (current-column))
+ (nb-cols (1- (length tabulated-list-format)))
+ (col-nb 0)
+ (total-width tabulated-list-padding)
+ (found nil))
+ (while (and (not found) (< col-nb nb-cols))
+ (if (>= start
+ (setq total-width
+ (+ total-width
+ (cadr (aref tabulated-list-format col-nb))
+ (or (plist-get (nthcdr 3 (aref tabulated-list-format
+ col-nb))
+ :pad-right)
+ 1))))
+ (setq col-nb (1+ col-nb))
+ (setq found t)))
+ col-nb))
+
+(defun kubed-list-fit-column-width-to-content (n)
+ "Fit width of Nth table column to its content.
+
+If N is negative, fit all columns. Interactively, N is the column
+number at point, or the numeric prefix argument if you provide one."
+ (interactive
+ (list (if current-prefix-arg
+ (prefix-numeric-value current-prefix-arg)
+ (kubed-list-column-number-at-point)))
+ kubed-list-mode)
+ (if (< n 0)
+ ;; Fit all columns.
+ (let* ((num-cols (length tabulated-list-format))
+ (widths (apply #'vector (seq-map
+ ;; +2 to make room for sorting icon.
+ (lambda (col) (+ 2 (length (car col))))
+ tabulated-list-format))))
+ (save-excursion
+ (goto-char (point-min))
+ (while-let ((entry (tabulated-list-get-entry)))
+ (dotimes (i num-cols)
+ (aset widths i (max (aref widths i) (length (aref entry i)))))
+ (forward-line)))
+ (setq tabulated-list-format (copy-tree tabulated-list-format t))
+ (dotimes (i num-cols)
+ (setf (cadr (aref tabulated-list-format i))
+ (1+ (aref widths i)))))
+ ;; Fit Nth column.
+ (let* ((width (+ 2 (length (car (aref tabulated-list-format n))))))
+ (save-excursion
+ (goto-char (point-min))
+ (while-let ((entry (tabulated-list-get-entry)))
+ (setq width (max width (length (aref entry n))))
+ (forward-line)))
+ (setq tabulated-list-format (copy-tree tabulated-list-format t))
+ (setf (cadr (aref tabulated-list-format n)) (1+ width))))
+ (tabulated-list-print t)
+ (tabulated-list-init-header))
+
+(declare-function kubed-list-transient "kubed-transient" ())
+(declare-function kubed-transient-logs-for-pod "kubed-transient" (val))
+(declare-function kubed-transient-logs-for-deployment "kubed-transient" (val))
+(declare-function kubed-transient-logs-for-statefulset "kubed-transient" (val))
+(declare-function kubed-transient-logs-for-replicaset "kubed-transient" (val))
+(declare-function kubed-transient-logs-for-job "kubed-transient" (val))
+(declare-function kubed-transient-logs-for-service "kubed-transient" (val))
+
+(defvar-keymap kubed-list-mode-map
+ :doc "Common keymap for Kubernetes resource list buffers."
+ "RET" #'kubed-list-select-resource
+ "o" #'kubed-list-select-resource-other-window
+ "C-o" #'kubed-list-display-resource
+ "D" #'kubed-list-delete
+ "P" #'kubed-list-patch
+ "x" #'kubed-list-delete-marked
+ "e" #'kubed-list-edit
+ "!" #'kubed-list-kubectl-command
+ "g" #'kubed-list-update
+ "/" #'kubed-list-set-filter
+ "|" #'kubed-list-fit-column-width-to-content
+ "d" #'kubed-list-mark-for-deletion
+ "u" #'kubed-list-unmark
+ "w" #'kubed-list-copy-as-kill
+ "C-i" #'kubed-list-next-column
+ "TAB" #'kubed-list-next-column
+ "C-S-i" #'kubed-list-previous-column
+ "S-TAB" #'kubed-list-previous-column
+ "<backtab>" #'kubed-list-previous-column
+ "+" #'kubed-list-create
+ "?" #'kubed-list-transient)
+
+(defun kubed-list-entries ()
+ "`tabulated-list-entries' function for `kubed-list-mode'."
+ (let ((pred (kubed-list-interpret-filter))
+ (ents nil))
+ (dolist (ent (alist-get 'resources (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (when (funcall pred ent) (push ent ents)))
+ (reverse ents)))
+
+;;;###autoload
+(defun kubed-list-handle-bookmark (bookmark)
+ "Display Kubernetes resource according to BOOKMARK."
+ (require 'bookmark)
+ (let* ((type (bookmark-prop-get bookmark 'type))
+ (context (bookmark-prop-get bookmark 'context))
+ (namespace (bookmark-prop-get bookmark 'namespace))
+ (current (bookmark-prop-get bookmark 'current))
+ (buff-fn (intern (format "kubed-%s-buffer" type)))
+ (buff (apply buff-fn context (when namespace (list namespace)))))
+ (set-buffer buff)
+ (when current (kubed-list-go-to-line current))))
+
+(put 'kubed-list-handle-bookmark 'bookmark-handler-type "KubedList")
+
+(defun kubed-list-make-bookmark ()
+ "Return bookmark pointing to currently displayed Kubernetes resource."
+ (require 'bookmark)
+ (cons
+ (concat kubed-list-type
+ (when kubed-list-namespace (concat "@" kubed-list-namespace))
+ (when kubed-list-context (concat "[" kubed-list-context "]")))
+ (append
+ (list
+ (cons 'handler #'kubed-list-handle-bookmark)
+ (cons 'filter kubed-list-filter)
+ (cons 'type kubed-list-type)
+ (cons 'context kubed-list-context)
+ (cons 'current (tabulated-list-get-id))
+ (cons 'namespace kubed-list-namespace)))))
+
+(defun kubed-list-revert (&rest _)
+ "Revert the current Kubernetes resources list buffer.
+
+This is the `revert-buffer-function' for `kubed-list-mode' and its
+derivatives. This function does not fetch new data from Kubernetes, it
+only (re)displays the existing data."
+ (let (marks)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (unless (eq (char-after) ?\s)
+ (push (cons (tabulated-list-get-id)
+ ;; Preserve mark text properties.
+ (buffer-substring (point) (1+ (point))))
+ marks))
+ (forward-line)))
+ (tabulated-list-revert)
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (let ((id (tabulated-list-get-id)))
+ (when-let ((mark (alist-get id marks nil nil #'equal)))
+ (tabulated-list-put-tag mark)))
+ (forward-line)))))
+
+(define-derived-mode kubed-list-mode tabulated-list-mode "Kubernetes Resources"
+ "Major mode for listing Kubernetes resources.
+
+Modes for specific resource types, such as `kubed-pods-mode', use this
+mode as their parent."
+ :interactive nil
+ (setq-local truncate-string-ellipsis (propertize ">" 'face 'shadow)
+ tabulated-list-entries #'kubed-list-entries
+ bookmark-make-record-function #'kubed-list-make-bookmark
+ revert-buffer-function #'kubed-list-revert)
+ (add-hook 'context-menu-functions #'kubed-list-context-menu nil t))
+
+;;;###autoload
+(defun kubed-delete-resources (type resources context &optional namespace)
+ "Delete Kubernetes RESOURCES of type TYPE in context CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCES. With a prefix argument \
+\\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \
+\\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well."
+ (interactive
+ (let ((type nil) (context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-delete))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless type
+ (setq type (kubed-read-resource-type "Type of resource to delete"
+ nil context)))
+ (when (and (null namespace) (kubed-namespaced-p type context))
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list type (kubed-read-resource-name type "Delete" nil t
+ context namespace)
+ context namespace)))
+ (unless resources (user-error "You didn't specify %s to delete" type))
+ (message (format "Deleting Kubernetes %s `%s'..."
+ type (string-join resources "', `")))
+ (if (zerop (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "delete" type
+ (append (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ resources)))
+ (message (format "Deleting Kubernetes %s `%s'... Done."
+ type (string-join resources "', `")))
+ (error (format "Failed to delete Kubernetes %s `%s'"
+ type (string-join resources "', `")))))
+
+;;;###autoload
+(defun kubed-edit-resource (type resource context &optional namespace)
+ "Edit Kubernetes RESOURCE of type TYPE in context CONTEXT.
+
+For namespaced resource types, NAMESPACE is the namespace of RESOURCE.
+
+Interactively, use the current context and namespace by default, and
+prompt for TYPE and RESOURCES. With a prefix argument \
+\\[universal-argument],
+prompt for NAMESPACE. With a double prefix argument \
+\\[universal-argument] \\[universal-argument],
+prompt for CONTEXT as well."
+ (interactive
+ (let ((type nil) (context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-edit))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless type
+ (setq type (kubed-read-resource-type "Type of resource to edit"
+ nil context)))
+ (when (and (null namespace) (kubed-namespaced-p type context))
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list type (kubed-read-resource-name type "Edit" nil nil
+ context namespace)
+ context namespace)))
+ (unless (bound-and-true-p server-process) (server-start))
+ (let ((process-environment
+ (cons (if (boundp 'emacsclient-program-name)
+ (concat "KUBE_EDITOR=" emacsclient-program-name)
+ "KUBE_EDITOR=emacsclient")
+ process-environment)))
+ (apply #'start-process
+ (format "*kubed-%S-edit*" type) nil
+ kubed-kubectl-program "edit" type resource
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))))))
+
+(defcustom kubed-list-mode-line-format
+ '(:eval (cond
+ ((process-live-p
+ (alist-get 'process
+ (kubed--alist kubed-list-type
+ kubed-list-context
+ kubed-list-namespace)))
+ (propertize "[...]" 'help-echo "Updating..."))
+ (kubed-list-filter
+ (propertize
+ (concat "[" (mapconcat #'prin1-to-string kubed-list-filter " ") "]")
+ 'help-echo "Current filter"))))
+ "Mode line construct for indicating status of Kubernetes resources list."
+ :type 'sexp
+ :risky t)
+
+;;;###autoload
+(defmacro kubed-define-resource (resource &optional properties &rest commands)
+ "Define Kubernetes RESOURCE with associated PROPERTIES and COMMANDS.
+
+RESOURCE is a symbol corresponding to a Kubernetes resource type, such
+as `pod' or `service'. This macro defines the following commands for
+interacting with Kubernetes RESOURCEs:
+
+- `kubed-display-RESOURCE': prompt for a RESOURCE and display its
+ description in YAML format. See also `kubed-yaml-setup-hook'.
+- `kubed-edit-RESOURCE': prompt for a RESOURCE and edit it.
+- `kubed-delete-RESOURCE': prompt for a RESOURCE and delete it.
+- `kubed-list-RESOURCEs': display a buffer listing all RESOURCEs in the
+ current namespace. The RESOURCEs list buffer uses a dedicated major
+ mode, `kubed-RESOURCEs-mode', which is also defined by this macro.
+- `kubed-create-RESOURCE': create a RESOURCE from a YAML or a JSON file.
+- `kubed-explain-RESOURCEs': show buffer with help about RESOURCEs.
+
+This macro also defines a prefix keymap, `kubed-RESOURCE-prefix-map',
+with bindings for the above commands.
+
+PROPERTIES is a list of lists (PROP JSON-PATH WIDTH SORT FORMAT . ATTRS)
+that specify properties of RESOURCEs. PROP is the name of the property,
+as a symbol; JSON-PATH is a JSONPath expression that evaluates to the
+value of PROP when applied to the JSON representation of a RESOURCE.
+WIDTH, SORT, FORMAT and ATTRS are optional and can be omitted. WIDTH is
+used as the default width of the column corresponding to PROP in
+RESOURCEs list buffers; SORT is sort predicate, a function that takes
+two values of PROP as strings and return non-nil if the first should
+sort before the second; FORMAT is a function that takes a value of PROP
+and formats it; and ATTRS is a plist of additional attributes of the
+PROP column, see `tabulated-list-format' for available attributes. For
+example, (phase \".status.phase\" 10) says that RESOURCE has a `phase'
+property at JSONPath \".status.phase\" whose values are typically 10
+columns wide. The first property in PROPERTIES, is used to annotate
+completion candidates when prompting for a RESOURCE.
+
+COMMANDS is a list of elements (COMMAND KEYS DOC-PREFIX . BODY) that
+define commands for RESOURCE list buffers. COMMAND is a symbol
+specifying the suffix of the command name, the full name of the command
+is `kubed-RESOURCEs-COMMAND' (for example, `kubed-pods-shell'); KEYS is
+either a string that specifies a key sequence to bind to the command in
+`kubed-RESOURCEs-mode-map', or nil if the command should not be bound;
+DOC-PREFIX is a string used to construct the docstring of the command,
+this macro appends the string \" Kubernetes RESOURCE at point.\" to it
+to obtain the final docstring; lastly, BODY is the body the command.
+Within BODY, the variable RESOURCE is let-bound to the name of the
+RESOURCE at point. For example, if RESOURCE is `pod', the following
+COMMANDS element defines a command `kubed-pods-frob' and binds it to the
+key \"f\" in `kubed-pods-mode-map':
+
+ (frob \"f\" \"Frobnicate\"
+ (message \"Preparing...\")
+ (frobnicate-pod pod)
+ (message \"Done.\"))
+
+By default, this macro assumes that RESOURCE is namespaced. To define a
+namespaceless resource type, put `:namespaced nil' before COMMANDS:
+
+ (kubed-define-resource global-thingy (PROP1 PROP2 ...) :namespaced nil
+ CMD1
+ CMD2
+ ...)
+
+Other keyword arguments that go between PROPERTIES and COMMANDS are:
+
+- `:create (ARGLIST DOCSTRING INTERACTIVE BODY...)': specialize the
+ resource creation command, `kubed-create-RESOURCE'. ARGLIST,
+ DOCSTRING, INTERACTIVE and BODY have the same meaning as in `defun'.
+- `:prefix ((KEY LABEL DEFINITION)...)': additional keybinding for the
+ prefix keymap `kubed-RESOURCE-prefix-map' and the
+ `kubed-RESOURCE-menu-map' menu. Each element (KEY LABEL DEFINITION)
+ says to bind KEY to DEFINITION in `kubed-RESOURCE-menu-map', and to
+ add DEFINITION to `kubed-RESOURCE-menu-map' with the label LABEL.
+- `:plural PLURAL': specify plural form of RESOURCE, as a symbol. If
+ you omit this keyword argument, the plural form defaults to RESOURCE
+ followed by \"s\".
+- `:logs t': generate `kubed-logs-for-RESOURCE' command.
+- `:suffixes (SUFFIX...)': add type-specific transient suffixes to
+ `kubed-list-transient'."
+ (declare (indent 2))
+ (let ((plrl-var (intern (format "%Ss" resource)))
+ (read-fun (intern (format "kubed-read-%S" resource)))
+ (dsp-name (intern (format "kubed-display-%S" resource)))
+ (edt-name (intern (format "kubed-edit-%S" resource)))
+ (crt-name (intern (format "kubed-create-%S" resource)))
+ (map-name (intern (format "kubed-%S-prefix-map" resource)))
+ (menu-map (intern (format "kubed-%S-menu-map" resource)))
+ (logs-cmd (intern (format "kubed-logs-for-%S" resource)))
+ (logs-trs (intern (format "kubed-transient-logs-for-%S" resource)))
+ (namespaced t) (logs nil)
+ (keyword nil)
+ frmt-var buff-fun list-cmd expl-cmd dlt-name mod-name
+ ctxt-fun crt-spec prf-keys hist-var trs-cols)
+
+ ;; Process keyword arguments.
+ (while (keywordp (car commands))
+ (setq keyword (pop commands))
+ (cond
+ ((eq keyword :namespaced) (setq namespaced (pop commands)))
+ ((eq keyword :logs) (setq logs (pop commands)))
+ ((eq keyword :create) (setq crt-spec (pop commands)))
+ ((eq keyword :prefix) (setq prf-keys (pop commands)))
+ ((eq keyword :plural) (setq plrl-var (pop commands)))
+ ((eq keyword :suffixes) (setq trs-cols (pop commands)))
+ ;; FIXME: Add error for unknown keywords.
+ (t (pop commands))))
+
+ (setq frmt-var (intern (format "kubed-%S-columns" plrl-var))
+ hist-var (intern (format "kubed-%S-history" plrl-var))
+ buff-fun (intern (format "kubed-%S-buffer" plrl-var))
+ list-cmd (intern (format "kubed-list-%S" plrl-var))
+ expl-cmd (intern (format "kubed-explain-%S" plrl-var))
+ dlt-name (intern (format "kubed-delete-%S" plrl-var))
+ mod-name (intern (format "kubed-%S-mode" plrl-var))
+ ctxt-fun (intern (format "kubed-%S-context-menu" plrl-var)))
+
+ (when logs (push `("L" "Show Logs" ,logs-cmd) prf-keys))
+
+ ;; Generate code.
+ `(progn
+ (setf (alist-get ,(symbol-name plrl-var) kubed--columns nil nil #'string=)
+ (list
+ '("NAME:.metadata.name")
+ ,@(mapcar (lambda (p)
+ `(cons ,(concat (upcase (symbol-name (car p)))
+ ":"
+ (cadr p))
+ ,(nth 4 p)))
+ properties)))
+
+ (defvar ,hist-var nil
+ ,(format "History list for `%S'." read-fun))
+
+ (defun ,read-fun (prompt &optional default multi context
+ . ,(when namespaced '(namespace)))
+ ,(format "Prompt with PROMPT for a Kubernetes %S name.
+
+Optional argument DEFAULT is the minibuffer default argument.
+
+Non-nil optional argument MULTI says to read and return a list
+of %S, instead of just one." resource plrl-var)
+ (kubed-read-resource-name ,(symbol-name plrl-var) prompt default multi context . ,(when namespaced '(namespace))))
+
+ (defun ,dsp-name (,resource &optional context . ,(when namespaced '(namespace)))
+ ,(if namespaced
+ (format "Display Kubernetes %S %s in CONTEXT and NAMESPACE.
+
+Interactively, use the current context and namespace by default. With a
+prefix argument \\[universal-argument], prompt for NAMESPACE. With a
+double prefix argument \\[universal-argument] \\[universal-argument], \
+prompt for CONTEXT as well." resource (upcase (symbol-name resource)))
+ (format "Display Kubernetes %S %s in context CONTEXT.
+
+Interactively, use the current context. With a prefix argument
+\\[universal-argument], prompt for CONTEXT." resource (upcase (symbol-name resource))))
+ (interactive
+ ,(if namespaced
+ `(let ((context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-display))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (,read-fun "Display" nil nil context namespace) context namespace))
+ `(let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-display))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (,read-fun "Display" nil nil context) context))))
+ (let ((context (or context (kubed-local-context))))
+ (kubed-display-resource
+ ,(symbol-name plrl-var) ,resource context
+ . ,(when namespaced '((or namespace (kubed--namespace context)))))))
+
+ (defun ,edt-name (,resource &optional context . ,(when namespaced '(namespace)))
+ ,(if namespaced
+ (format "Edit Kubernetes %S %s in CONTEXT and NAMESPACE.
+
+Interactively, use the current context and namespace by default. With a
+prefix argument \\[universal-argument], prompt for NAMESPACE. With a
+double prefix argument \\[universal-argument] \\[universal-argument], \
+prompt for CONTEXT as well." resource (upcase (symbol-name resource)))
+ (format "Edit Kubernetes %S %s in context CONTEXT.
+
+Interactively, use the current context. With a prefix argument
+\\[universal-argument], prompt for CONTEXT." resource (upcase (symbol-name resource))))
+ (interactive
+ ,(if namespaced
+ `(let ((context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-edit))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (,read-fun "Edit" nil nil context namespace) context namespace))
+ `(let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-edit))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (,read-fun "Edit" nil nil context) context))))
+ (let ((context (or context (kubed-local-context))))
+ (kubed-edit-resource
+ ,(symbol-name plrl-var) ,resource context
+ . ,(when namespaced '((or namespace (kubed--namespace context)))))))
+
+ (defun ,dlt-name (,plrl-var &optional context
+ . ,(when namespaced '(namespace)))
+ ,(if namespaced
+ (format "Delete Kubernetes %S %s in CONTEXT and NAMESPACE.
+
+Interactively, use the current context and namespace by default. With a
+prefix argument \\[universal-argument], prompt for NAMESPACE. With a
+double prefix argument \\[universal-argument] \\[universal-argument], \
+prompt for CONTEXT as well." plrl-var (upcase (symbol-name plrl-var)))
+ (format "Delete Kubernetes %S %s in context CONTEXT.
+
+Interactively, use the current context. With a prefix argument
+\\[universal-argument], prompt for CONTEXT." plrl-var (upcase (symbol-name plrl-var))))
+ (interactive
+ ,(if namespaced
+ `(let ((context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-delete))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (,read-fun "Delete" nil t context namespace) context namespace))
+ `(let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-delete))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (,read-fun "Delete" nil t context) context))))
+ (unless ,plrl-var
+ (user-error ,(format "You didn't specify %S to delete" plrl-var)))
+ (let ((context (or context (kubed-local-context))))
+ (kubed-delete-resources
+ ,(symbol-name plrl-var) ,plrl-var context
+ . ,(when namespaced '((or namespace (kubed--namespace context)))))))
+
+ ,(if crt-spec `(defun ,crt-name . ,crt-spec)
+ `(defun ,crt-name (definition &optional context)
+ ,(format "Create Kubernetes %s from file DEFINITION in CONTEXT.
+
+Interactively, prompt for DEFINITION and use the current context. With
+a prefix argument \\[universal-argument], prompt for CONTEXT too."
+ (symbol-name resource))
+ (interactive
+ (let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (kubed-read-resource-definition-file-name ,(symbol-name resource))
+ context)))
+ (kubed-create definition ,(symbol-name resource) context)
+ (kubed-update ,(symbol-name plrl-var) context)))
+
+ ,@(when logs
+ `((defun ,logs-cmd
+ ( ,resource &optional context namespace
+ container follow limit prefix since tail timestamps)
+ ,(format "Show logs for Kubernetes %S %s."
+ resource (upcase (symbol-name resource)))
+ (interactive
+ (let ( ,resource context namespace
+ container follow limit prefix since tail timestamps)
+ (dolist (arg (kubed-transient-args
+ ',logs-trs))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--limit-bytes=\\(.+\\)" arg)
+ (setq limit (string-to-number (match-string 1 arg))))
+ ((string-match "--tail=\\(.+\\)" arg)
+ (setq tail (string-to-number (match-string 1 arg))))
+ ((string-match "--since-time=\\(.+\\)" arg)
+ (setq since (match-string 1 arg)))
+ ((equal "--all-containers" arg) (setq container t))
+ ((equal "--follow" arg) (setq follow t))
+ ((equal "--prefix" arg) (setq prefix t))
+ ((equal "--timestamps" arg) (setq timestamps t))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (setq ,resource (,read-fun "Show logs for"
+ (and (equal context kubed-list-context)
+ (equal namespace kubed-list-namespace)
+ (equal ,(symbol-name plrl-var) kubed-list-type)
+ (tabulated-list-get-id (mouse-set-point last-nonmenu-event)))
+ nil context namespace))
+ (list ,resource context namespace
+ ,(if (eq resource 'pod)
+ '(or container
+ (kubed-read-container pod "Container" t
+ context namespace))
+ 'container)
+ follow limit prefix since tail timestamps)))
+ (kubed-logs ,(symbol-name plrl-var) ,resource context namespace container
+ follow limit prefix since tail timestamps))))
+
+ ,@(let ((click-var (gensym "click")))
+ (mapcar
+ (pcase-lambda (`(,suffix ,_key ,desc . ,body))
+ `(defun ,(intern (format "kubed-%S-%S" plrl-var suffix)) (,click-var)
+ ,(format "%s Kubernetes %S at point." desc resource)
+ (interactive (list last-nonmenu-event) ,mod-name)
+ (if-let ((,resource (tabulated-list-get-id (mouse-set-point ,click-var))))
+ (progn ,@body)
+ (user-error ,(format "No Kubernetes %S at point" resource)))))
+ commands))
+
+ (defvar-keymap ,(intern (format "kubed-%S-mode-map" plrl-var))
+ :doc ,(format "Keymap for `%S" mod-name)
+ "+" #',crt-name
+ ,@(when logs
+ `("l" #'kubed-list-logs
+ "L" #',logs-trs))
+ ,@(mapcan
+ (pcase-lambda (`(,suffix ,key ,_desc . ,_body))
+ (when key
+ (list key `#',(intern (format "kubed-%S-%S" plrl-var suffix)))))
+ commands))
+
+ (defvar ,frmt-var
+ ',(let ((i 0)
+ (res nil))
+ (dolist (p properties)
+ (setq i (1+ i))
+ (push
+ (append
+ (list (capitalize (symbol-name (car p)))
+ (caddr p)
+ (if-let ((sorter (cadddr p)))
+ `(lambda (l r)
+ (funcall ,sorter (aref (cadr l) ,i) (aref (cadr r) ,i)))
+ t))
+ (nthcdr 5 p))
+ res))
+ (reverse res)))
+
+ (defun ,ctxt-fun (menu . ,(if commands '(click) '(_click)))
+ ,@(when commands
+ `((when (tabulated-list-get-entry (posn-point (event-start click)))
+ ,@(mapcar
+ (pcase-lambda (`(,suffix ,_key ,desc . ,_body))
+ `(define-key
+ menu [,(intern (format "kubed-%S-%S" plrl-var suffix))]
+ (list 'menu-item ,(format "%s this %S" desc resource)
+ #',(intern (format "kubed-%S-%S" plrl-var suffix)))))
+ (reverse commands)))))
+ menu)
+
+ (define-derived-mode ,mod-name kubed-list-mode
+ '(,(format "Kubernetes %s" (capitalize (symbol-name plrl-var)))
+ kubed-list-mode-line-format)
+ ,(format "Major mode for listing Kubernetes %S." plrl-var)
+ :interactive nil
+ (setq kubed-list-filter-history-variable
+ ',(intern (format "kubed-%S-filter-history" plrl-var))
+ kubed-list-type ,(symbol-name plrl-var)
+ ,@(when trs-cols
+ `(kubed-list-transient-extra-suffixes ',trs-cols))
+ tabulated-list-padding 2
+ tabulated-list-format (apply #'vector (cons kubed-name-column ,frmt-var)))
+ (add-hook 'context-menu-functions #',ctxt-fun nil t)
+ (tabulated-list-init-header))
+
+ (defun ,buff-fun (context . ,(when namespaced '(namespace)))
+ (let ((buf-name (format ,(format "*Kubed %S%%s*" plrl-var)
+ ,(if namespaced
+ `(concat "@" namespace "[" context "]")
+ `(concat "[" context "]")))))
+ (if-let ((buf (get-buffer buf-name))) buf
+ (with-current-buffer (get-buffer-create buf-name)
+ (,mod-name)
+ (setq kubed-list-context context
+ ,@(when namespaced
+ '(kubed-list-namespace namespace)))
+ (kubed-list-update)
+ (tabulated-list-print)
+ (current-buffer)))))
+
+ (defun ,list-cmd (context . ,(when namespaced '(namespace)))
+ ,(if namespaced
+ (format "List Kubernetes %S in context CONTEXT and namespace NAMESPACE.
+
+Interactively, use the current context and namespace by default. With a
+prefix argument \\[universal-argument], prompt for NAMESPACE. With a
+double prefix argument \\[universal-argument] \\[universal-argument], \
+prompt for CONTEXT as well." plrl-var)
+ (format "List Kubernetes %S in context CONTEXT.
+
+Interactively, use the current context. With a prefix argument
+\\[universal-argument], prompt for CONTEXT." plrl-var))
+ (interactive
+ (let* ((context (kubed-local-context))
+ (context (if (equal current-prefix-arg
+ ,(if namespaced ''(16) ''(4)))
+ (kubed-read-context "Context" context)
+ context)))
+ (list context
+ . ,(when namespaced '((kubed--namespace context current-prefix-arg))))))
+ (pop-to-buffer (,buff-fun context . ,(when namespaced '(namespace)))))
+
+ (defun ,expl-cmd ()
+ ,(format "Show help buffer with explanation about Kubernetes %S." plrl-var)
+ (interactive)
+ (kubed-explain ,(symbol-name plrl-var)))
+
+ (defvar-keymap ,map-name
+ :doc ,(format "Prefix keymap for Kubed %S commands." resource)
+ :prefix ',map-name
+ "l" #',list-cmd
+ "+" #',crt-name
+ "e" #',edt-name
+ "d" #',dlt-name
+ "RET" #',dsp-name
+ "E" #',expl-cmd
+ ,@(mapcan
+ (pcase-lambda (`(,key ,_label ,cmd))
+ (list key `#',cmd))
+ prf-keys))
+
+ (defvar-keymap ,menu-map
+ :doc ,(format "Keymap with Kubernetes %S related menu entries." resource)
+ :prefix ',menu-map
+ "<list>" '("List" . ,list-cmd)
+ "<create>" '("Create" . ,crt-name)
+ "<edit>" '("Edit" . ,edt-name)
+ "<delete>" '("Delete" . ,dlt-name)
+ "<display>" '("Display" . ,dsp-name)
+ "<explain>" '("Explain Fields" . ,expl-cmd)
+ ,@(mapcan
+ (pcase-lambda (`(,key ,label ,cmd))
+ (list key `'(,label . ,cmd)))
+ prf-keys)))))
+
+(setf
+ ;; Teach Imenu about `kubed-define-resource'.
+ (alist-get "KubedResource" lisp-imenu-generic-expression nil nil #'equal)
+ (list (concat "^\\s-*(kubed-define-resource\\s-+\\("
+ lisp-mode-symbol-regexp
+ "\\)")
+ 1))
+
+(defun kubed-remote-file-name (context namespace pod &optional file-name)
+ "Return remote FILE-NAME for POD in NAMESPACE and CONTEXT."
+ (concat "/" kubed-tramp-method ":"
+ context "%" namespace "%" pod
+ "%" (kubed-read-container pod "Container" t context namespace)
+ ":" file-name))
+
+(declare-function kubed-tramp-context "kubed-tramp" (file-name))
+(declare-function kubed-tramp-namespace "kubed-tramp" (file-name))
+(declare-function kubed-tramp-assert-support "kubed-tramp" ())
+
+;;;###autoload (autoload 'kubed-display-pod "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-pod "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-pods "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-pods "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-pod "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-pod "kubed" nil t)
+;;;###autoload (autoload 'kubed-pod-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource pod
+ ((phase ".status.phase" 10
+ nil ; sorting function
+ (lambda (ph)
+ (if-let ((face (pcase ph
+ ;; TODO: Define/derive bespoke faces.
+ ("Pending" 'warning)
+ ("Running" 'success)
+ ("Succeeded" 'shadow)
+ ("Failed" 'error))))
+ (propertize ph 'face face)
+ ph)))
+ (ready ".status.containerStatuses[?(.ready==true)].name" 6
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ (lambda (cs)
+ (if (string= cs "<none>") "0"
+ (number-to-string (1+ (seq-count (lambda (c) (= c ?,)) cs)))))
+ :right-align t)
+ (total ".status.containerStatuses[*].name" 6
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ (lambda (cs)
+ (if (string= cs "<none>") "0"
+ (number-to-string (1+ (seq-count (lambda (c) (= c ?,)) cs)))))
+ :right-align t)
+ (starttime ".status.startTime" 20))
+ :logs t
+ :prefix (("A" "Attach" kubed-attach)
+ ("X" "Execute" kubed-exec)
+ ("F" "Forward Port" kubed-forward-port-to-pod))
+ :suffixes ([ ("L" "Logs" kubed-transient-logs-for-pod)
+ ("X" "Exec" kubed-pods-exec)
+ ("a" "Attach" kubed-pods-attach)]
+ [ :pad-keys t
+ ("C-d" "Dired" kubed-pods-dired)
+ ("s" "Shell" kubed-pods-shell)
+ ("F" "Forward port" kubed-pods-forward-port)])
+ (dired "C-d" "Start Dired in"
+ (kubed-tramp-assert-support)
+ (dired (kubed-remote-file-name
+ kubed-list-context kubed-list-namespace pod)))
+ (shell "s" "Start shell in"
+ (kubed-tramp-assert-support)
+ (let* ((default-directory (kubed-remote-file-name
+ kubed-list-context kubed-list-namespace pod)))
+ (shell
+ (concat "*Kubed Shell "
+ (kubed-display-resource-short-description
+ "pods" pod kubed-list-context kubed-list-namespace)
+ "*"))))
+ (attach "a" "Attach to remote process running on"
+ (kubed-attach pod (kubed-read-container pod "Container" t
+ kubed-list-context
+ kubed-list-namespace)
+ kubed-list-context kubed-list-namespace t t))
+ (exec "X" "Execute command in"
+ (let ((container (kubed-read-container pod "Container" t
+ kubed-list-context
+ kubed-list-namespace))
+ (cmd-args (split-string-and-unquote
+ (read-string "Execute command: "))))
+ (kubed-exec pod (car cmd-args) container kubed-list-context
+ kubed-list-namespace t t (cdr cmd-args))))
+ (forward-port "F" "Forward local network port to remote port of"
+ (let ((local-port (read-number "Forward local port: ")))
+ (kubed-forward-port-to-pod
+ pod local-port
+ (read-number (format "Forward local port %d to remote port: "
+ local-port))
+ kubed-list-context
+ kubed-list-namespace))))
+
+;;;###autoload (autoload 'kubed-display-namespace "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-namespace "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-namespaces "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-namespaces "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-namespace "kubed" nil t)
+;;;###autoload (autoload 'kubed-namespace-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource namespace
+ ((phase ".status.phase" 10
+ nil ; sorting function
+ (lambda (ph)
+ (if-let ((face (pcase ph
+ ;; TODO: Define/derive bespoke faces.
+ ("Active" 'success)
+ ("Terminating" 'shadow))))
+ (propertize ph 'face face)
+ ph)))
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :namespaced nil
+ :prefix (("S" "Set" kubed-set-namespace))
+ :create
+ ((name &optional context)
+ "Create Kubernetes namespace NAME in CONTEXT.
+
+Interactively, prompt for NAME and use the current context by default.
+With a prefix argument, prompt for CONTEXT instead."
+ (interactive
+ (let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (read-string "Create namespace with name: ") context)))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "namespace" name
+ (when context (list "--context" context))))
+ (user-error "Failed to create Kubernetes namespace with name `%s'" name))
+ (kubed-update "namespaces" (or context (kubed-local-context)))
+ (message "Created Kubernetes namespace with name `%s'." name))
+ (set "s" "Set current namespace to"
+ (save-excursion
+ (goto-char (point-min))
+ (while (not (eobp))
+ (when (eq (char-after) ?*)
+ (tabulated-list-put-tag " "))
+ (forward-line)))
+ (kubed-set-namespace namespace kubed-list-context)
+ (tabulated-list-put-tag
+ (propertize "*" 'help-echo "Current namespace"))))
+
+;;;###autoload (autoload 'kubed-display-persistentvolume "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-persistentvolume "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-persistentvolumes "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-persistentvolumes "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-persistentvolume "kubed" nil t)
+;;;###autoload (autoload 'kubed-persistentvolume-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource persistentvolume () :namespaced nil)
+
+;;;###autoload (autoload 'kubed-display-service "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-service "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-services "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-services "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-service "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-service "kubed" nil t)
+;;;###autoload (autoload 'kubed-service-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource service
+ ((type ".spec.type" 12)
+ (clusterip ".spec.clusterIP" 16)
+ (externalip ".spec.externalIPs[*]" 16)
+ (ports ".spec.ports[*].port" 6)
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :logs t)
+
+;;;###autoload (autoload 'kubed-display-secret "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-secret "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-secrets "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-secrets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-secret "kubed" nil t)
+;;;###autoload (autoload 'kubed-secret-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource secret
+ ((type ".type" 32) (creationtimestamp ".metadata.creationTimestamp" 20)))
+
+(defun kubed-create-job-from-cronjob (name cronjob &optional context namespace)
+ "Create Kubernetes job with name NAME from cronjob CRONJOB.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace to use for the job,
+defaulting to the current namespace."
+ (interactive
+ (let ((name (read-string "Create job with name: "))
+ (context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create-job))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list name
+ (kubed-read-cronjob
+ (format "Create job `%s' from cronjob" name) nil nil context namespace)
+ context namespace)))
+ (let* ((context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context))))
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil nil nil
+ "create" "job" name "--from" (concat "cronjob/" cronjob)
+ "-n" namespace "--context" context))
+ (user-error "Failed to create Kubernetes job `%s'" name))
+ (kubed-update "jobs" context namespace)
+ (message "Created Kubernetes job `%s'." name)))
+
+;;;###autoload (autoload 'kubed-display-job "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-job "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-jobs "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-jobs "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-job "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-job "kubed" nil t)
+;;;###autoload (autoload 'kubed-job-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource job
+ ((status ".status.conditions[0].type" 10) (starttime ".status.startTime" 20))
+ :prefix (("c" "Create from Cron" kubed-create-job-from-cronjob))
+ :logs t
+ :create
+ ((name image &optional context namespace command)
+ "Create Kubernetes job with name NAME executing COMMAND in IMAGE.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace to use for the job,
+defaulting to the current namespace."
+ (interactive
+ (let ((name (read-string "Create job with name: "))
+ (image nil) (context nil) (namespace nil) (command nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create-job))
+ (cond
+ ((string-match "--image=\\(.+\\)" arg)
+ (setq image (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "-- =\\(.+\\)" arg)
+ (setq command (split-string-and-unquote (match-string 1 arg))))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (unless image
+ (setq image (kubed-read-container-image "Image to run in job")))
+ (list name image context namespace command)))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "job" name "--image" image
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ (when command (cons "--" command)))))
+ (user-error "Failed to create Kubernetes job `%s'" name))
+ (message "Created Kubernetes job `%s'." name)))
+
+;;;###autoload
+(defun kubed-watch-deployment-status (dep &optional context namespace cb)
+ "Show and update status of Kubernetes deployment DEP in a dedicate buffer.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace. CB is an optional callback function to call with
+no arguments when the deployment ends.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT."
+ (interactive
+ (let (context namespace)
+ (dolist (arg (kubed-transient-args 'kubed-transient-rollout))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (kubed-read-deployment "Watch deployment status" nil nil
+ context namespace)
+ context namespace)))
+ (let* ((buf (get-buffer-create "*kubed-deployment-status*"))
+ (context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context))))
+ (with-current-buffer buf (erase-buffer))
+ (make-process
+ :name "*kubed-watch-deployment-status*"
+ :buffer buf
+ :command (list kubed-kubectl-program "rollout" "status"
+ "deployment" dep "-n" namespace "--context" context)
+ :sentinel (when cb
+ (lambda (_proc status)
+ (when (member status '("finished\n"
+ "exited abnormally with code 1\n"))
+ (funcall cb)))))
+ (display-buffer buf)))
+
+(defcustom kubed-restart-deployment-watch-status t
+ "Whether to pop up a progress buffer when restarting Kubernetes deployments."
+ :type 'boolean)
+
+;;;###autoload
+(defun kubed-scale-deployment (dep reps &optional context namespace)
+ "Scale deployment DEP in namespace NAMESPACE via CONTEXT to REPS replicas.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT."
+ (interactive
+ (let (reps context namespace)
+ (dolist (arg (kubed-transient-args 'kubed-transient-rollout))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--replicas=\\(.+\\)" arg)
+ (setq reps (string-to-number (match-string 1 arg))))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (kubed-read-deployment "Scale deployment" nil nil context namespace)
+ (or reps (read-number "Number of replicas: ")) context namespace)))
+ (let* ((context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context))))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "scale" "deployment" dep
+ "--replicas" (number-to-string reps)
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
+ (user-error "Failed to scale Kubernetes deployment `%s'" dep))
+ (message "Scaled Kubernetes deployment `%s' to %d replicas." dep reps)))
+
+;;;###autoload
+(defun kubed-restart-deployment (dep &optional context namespace)
+ "Restart Kubernetes deployment DEP in namespace NAMESPACE via CONTEXT.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; NAMESPACE is the namespace of DEP, defaulting to
+the current namespace. CB is an optional callback function to call with
+no arguments when the deployment ends.
+
+Interactively, prompt for DEP. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT."
+ (interactive
+ (let (context namespace)
+ (dolist (arg (kubed-transient-args 'kubed-transient-rollout))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (kubed-read-deployment "Restart deployment" nil nil
+ context namespace)
+ context namespace)))
+ (let* ((context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context))))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "rollout" "restart" "deployment" dep
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
+ (user-error "Failed to restart Kubernetes deployment `%s'" dep))
+ (message "Restarting Kubernetes deployment `%s'." dep)
+ (when kubed-restart-deployment-watch-status
+ (kubed-watch-deployment-status
+ dep context namespace
+ (lambda ()
+ (kubed-update "deployments" context namespace))))))
+
+;;;###autoload (autoload 'kubed-display-deployment "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-deployment "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-deployments "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-deployments "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-deployment "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-deployment "kubed" nil t)
+;;;###autoload (autoload 'kubed-deployment-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource deployment
+ (( ready ".status.readyReplicas" 6
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ (lambda (s) (if (string= s "<none>") "0" s))
+ :right-align t)
+ ( updated ".status.updatedReplicas" 8
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ (lambda (s) (if (string= s "<none>") "0" s))
+ :right-align t)
+ ( available ".status.availableReplicas" 10
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ (lambda (s) (if (string= s "<none>") "0" s))
+ :right-align t)
+ ( reps ".status.replicas" 4
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ nil ; formatting function
+ :right-align t)
+ ( creationtimestamp ".metadata.creationTimestamp" 20))
+ :prefix (("R" "Restart" kubed-restart-deployment)
+ ("W" "Watch" kubed-watch-deployment-status)
+ ("$" "Scale" kubed-scale-deployment))
+ :logs t
+ :suffixes ([("L" "Logs" kubed-transient-logs-for-deployment)
+ ("W" "Watch" kubed-deployments-watch)
+ ("R" "Restart" kubed-deployments-restart)
+ ("$" "Scale" kubed-deployments-scale)])
+ :create
+ ((name images &optional context namespace replicas port command)
+ "Deploy IMAGES to Kubernetes in deployment with name NAME.
+
+Optional argument NAMESPACE is the namespace to use for the deployment,
+defaulting to the current namespace, REPLICAS in the number of replicas
+to create for each image, PORT is the port to expose, and COMMAND is an
+optional command to run in the images."
+ (interactive
+ (let ((name (read-string "Create deployment with name: "))
+ (images nil) (replicas nil) (port nil) (command nil)
+ (context nil) (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create-deployment))
+ (cond
+ ((string-match "--replicas=\\(.+\\)" arg)
+ (setq replicas (string-to-number (match-string 1 arg))))
+ ((string-match "--image=\\(.+\\)" arg)
+ (push (match-string 1 arg) images))
+ ((string-match "--port=\\(.+\\)" arg)
+ (setq port (string-to-number (match-string 1 arg))))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "-- =\\(.+\\)" arg)
+ (setq command (split-string-and-unquote (match-string 1 arg))))))
+ (unless images
+ (setq images (kubed-read-container-image "Images to deploy" nil t)))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list name images context namespace replicas port command)))
+ (let* ((context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context))))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "deployment" name
+ (append
+ (mapcar (lambda (image) (concat "--image=" image)) images)
+ (when namespace (list (concat "--namespace=" namespace)))
+ (when context (list (concat "--context=" context)))
+ (when replicas (list (format "--replicas=%d" replicas)))
+ (when port (list (format "--port=%d" port)))
+ (when command (cons "--" command)))))
+ (user-error "Failed to create Kubernetes deployment `%s'" name))
+ (message "Created Kubernetes deployment `%s'." name)
+ (kubed-update "deployments" context namespace)))
+ (restart "R" "Restart"
+ (kubed-restart-deployment deployment kubed-list-context kubed-list-namespace)
+ (unless kubed-restart-deployment-watch-status
+ (message "Deployment restarting")
+ (kubed-list-update t)))
+ (watch "W" "Watch" (kubed-watch-deployment-status
+ deployment kubed-list-context kubed-list-namespace))
+ (scale "$" "Scale" (kubed-scale-deployment
+ deployment (if current-prefix-arg
+ (prefix-numeric-value current-prefix-arg)
+ (read-number "Number of replicas: "))
+ kubed-list-context kubed-list-namespace)))
+
+;;;###autoload (autoload 'kubed-display-replicaset "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-replicaset "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-replicasets "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-replicasets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-replicaset "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-replicaset "kubed" nil t)
+;;;###autoload (autoload 'kubed-replicaset-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource replicaset
+ ((reps ".status.replicas" 4
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ nil ; formatting function
+ :right-align t)
+ (ownerkind ".metadata.ownerReferences[0].kind" 12)
+ (ownername ".metadata.ownerReferences[0].name" 16)
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :logs t)
+
+;;;###autoload (autoload 'kubed-display-statefulset "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-statefulset "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-statefulsets "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-statefulsets "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-statefulset "kubed" nil t)
+;;;###autoload (autoload 'kubed-logs-for-statefulset "kubed" nil t)
+;;;###autoload (autoload 'kubed-statefulset-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource statefulset
+ ((reps ".status.replicas" 4
+ (lambda (l r) (< (string-to-number l) (string-to-number r)))
+ nil ; formatting function
+ :right-align t)
+ (ownerkind ".metadata.ownerReferences[0].kind" 12)
+ (ownername ".metadata.ownerReferences[0].name" 16)
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :logs t)
+
+(defun kubed-cronjob-suspended-p (cj &optional context ns)
+ "Return non-nil if cronjob CJ in CONTEXT and namespace NS is suspended."
+ (equal (car (apply #'process-lines
+ kubed-kubectl-program
+ "get" "cronjobs" cj
+ "-o" "custom-columns=SUSPENDED:.spec.suspend" "--no-headers"
+ (append
+ (when ns (list "-n" ns))
+ (when context (list "--context" context)))))
+ "true"))
+
+;;;###autoload (autoload 'kubed-display-cronjob "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-cronjob "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-cronjobs "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-cronjobs "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-cronjob "kubed" nil t)
+;;;###autoload (autoload 'kubed-cronjob-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource cronjob
+ ((schedule ".spec.schedule" 20)
+ (suspend ".spec.suspend" 20)
+ (lastschedule ".status.lastScheduleTime" 20)
+ (lastsuccess ".status.lastSuccessfulTime" 20)
+ (activejob ".status.active[0].name" 36))
+ :create
+ ((name image schedule &optional context namespace command)
+ "Schedule IMAGE to run in a cronjob with name NAME according to SCHEDULE.
+
+Optional argument NAMESPACE is the namespace to use for the cronjob,
+defaulting to the current namespace. COMMAND is a list of strings that
+represent a program followed by its arguments, if it non-nil then it
+overrides the default command IMAGE runs."
+ (interactive
+ (let ((name (read-string "Create cronjob with name: "))
+ (image nil)
+ (schedule nil)
+ (command nil)
+ (context nil)
+ (namespace nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create-cronjob))
+ (cond
+ ((string-match "--image=\\(.+\\)" arg)
+ (setq image (match-string 1 arg)))
+ ((string-match "--schedule=\\(.+\\)" arg)
+ (setq schedule (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "-- =\\(.+\\)" arg)
+ (setq command (split-string-and-unquote (match-string 1 arg))))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (unless image
+ (setq image (kubed-read-container-image "Image to run")))
+ (unless schedule
+ (setq schedule (read-string "Cron schedule: " "* * * * *")))
+ (list name image schedule context namespace command)))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "cronjob" name
+ "--image" image "--schedule" schedule
+ (append
+ (when context (list "--context" context))
+ (when namespace (list "--namespace" namespace))
+ (when command (cons "--" command)))))
+ (user-error "Failed to create Kubernetes cronjob `%s'" name))
+ (message "Created Kubernetes cronjob `%s'." name))
+ ;; Commands in *kubed-cronjobs* buffer.
+ ( toggle-suspension "T" "Toggle suspension of"
+ (kubed-patch "cronjobs" cronjob
+ (format
+ "{\"spec\": {\"suspend\": %s}}"
+ (if (kubed-cronjob-suspended-p
+ cronjob kubed-list-context kubed-list-namespace)
+ "false" "true"))
+ kubed-list-context kubed-list-namespace))
+ ( create-job "j" "Create job from"
+ (kubed-create-job-from-cronjob
+ (read-string "Create job with name: ") cronjob
+ kubed-list-context kubed-list-namespace)))
+
+;;;###autoload (autoload 'kubed-display-ingressclass "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-ingressclass "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-ingressclasses "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-ingressclasss "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-ingressclass "kubed" nil t)
+;;;###autoload (autoload 'kubed-ingressclass-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource ingressclass
+ ((controller ".spec.controller" 32)
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :namespaced nil
+ :plural ingressclasses)
+
+;;;###autoload (autoload 'kubed-display-ingress "kubed" nil t)
+;;;###autoload (autoload 'kubed-edit-ingress "kubed" nil t)
+;;;###autoload (autoload 'kubed-delete-ingresses "kubed" nil t)
+;;;###autoload (autoload 'kubed-list-ingresss "kubed" nil t)
+;;;###autoload (autoload 'kubed-create-ingress "kubed" nil t)
+;;;###autoload (autoload 'kubed-ingress-prefix-map "kubed" nil t 'keymap)
+(kubed-define-resource ingress
+ ((class ".spec.ingressClassName" 8)
+ (creationtimestamp ".metadata.creationTimestamp" 20))
+ :plural ingresses
+ :create
+ ((name rules &optional context namespace class default-backend annotations)
+ "Create Kubernetes ingress with name NAME and rules RULES.
+
+Optional argument NAMESPACE is the namespace to use for the ingress,
+defaulting to the current namespace. CLASS is the ingress class,
+ANNOTATIONS are a list of annotations for the created ingress, and
+DEFAULT-BACKEND is the service to use as a backend for unhandled URLs."
+ (interactive
+ (let ((name (read-string "Create ingress with name: "))
+ (rules nil)
+ (context nil)
+ (namespace nil)
+ (class nil)
+ (annotations nil)
+ (default-backend nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create-ingress))
+ (cond
+ ((string-match "--rule=\\(.+\\)" arg)
+ (push (match-string 1 arg) rules))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--class=\\(.+\\)" arg)
+ (setq class (match-string 1 arg)))
+ ((string-match "--default-backend=\\(.+\\)" arg)
+ (setq default-backend (match-string 1 arg)))
+ ((string-match "--annotation=\\(.+\\)" arg)
+ (push (match-string 1 arg) annotations))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (unless rules (setq rules (kubed-read-ingress-rules)))
+ (list name rules context namespace class default-backend annotations)))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "create" "ingress" name
+ (append
+ (mapcan (lambda (rule) (list "--rule" rule)) rules)
+ (when namespace (list "--namespace" namespace))
+ (when context (list "--context" context))
+ (when class (list "--class" class))
+ (when default-backend
+ (list "--default-backend" default-backend))
+ (mapcan (lambda (ann) (list "--annotation" ann))
+ annotations))))
+ (user-error "Failed to create Kubernetes ingress `%s'" name))
+ (message "Created Kubernetes ingress `%s'." name)))
+
+;; TODO: Events may be numerous. Need to only get a few.
+;; ;;;###autoload (autoload 'kubed-list-events "kubed" nil t)
+;; ;;;###autoload (autoload 'kubed-event-prefix-map "kubed" nil t 'keymap)
+;; (kubed-define-resource event
+;; ((last ".lastTimestamp" 20)
+;; (type ".type" 10)
+;; (reason ".reason" 20)
+;; (objectkind ".involvedObject.kind" 12)
+;; (objectname ".involvedObject.name" 16)
+;; (message ".message" 36)))
+
+(defun kubed-contexts ()
+ "Return list of Kubernetes contexts."
+ (process-lines kubed-kubectl-program "config" "get-contexts" "-o" "name"))
+
+(defun kubed-current-context ()
+ "Return current Kubernetes context."
+ (car (process-lines kubed-kubectl-program "config" "current-context")))
+
+(defun kubed-read-default-namespace-non-empty (context)
+ "Prompt for a default namespace for CONTEXT, refusing empty input."
+ (let ((ns (kubed-read-namespace
+ (format "Default namespace for context `%s'" context)
+ nil nil context)))
+ (when (string-empty-p ns)
+ (user-error "You didn't specify a default namespace"))
+ ns))
+
+(defun kubed-default-context-and-namespace ()
+ "Return default context and namespace as a cons cell (CONTEXT . NAMESPACE)."
+ (or kubed-default-context-and-namespace
+ (setq kubed-default-context-and-namespace
+ (let ((context (kubed-current-context)))
+ (cons context
+ (or (kubed-current-namespace context)
+ (kubed-read-default-namespace-non-empty context)))))))
+
+(defun kubed-default-context ()
+ "Return default `kubectl' context."
+ (car (kubed-default-context-and-namespace)))
+
+(defun kubed-default-namespace ()
+ "Return default Kubernetes namespace in the default context."
+ (cdr (kubed-default-context-and-namespace)))
+
+(defun kubed-remote-file-name-p (file-name)
+ "Check whether FILE-NAME is a Kubed Tramp remote file name."
+ ;; We use this function to heuristically check file names without
+ ;; loading `kubed-tramp' (which in turn loads `tramp').
+ (string-match-p "[/|]kubedv[0-9]+:" file-name))
+
+(defun kubed-local-context ()
+ "Return Kubernetes context local to the current buffer."
+ (or kubed-list-context
+ (nth 2 kubed-display-resource-info)
+ (and (kubed-remote-file-name-p default-directory)
+ (kubed-tramp-context default-directory))
+ (kubed-default-context)))
+
+(defvar kubed-context-history nil
+ "History list for `kubed-read-context'.")
+
+(defun kubed-read-context (prompt &optional default)
+ "Prompt with PROMPT for a Kubernetes context.
+
+Optional argument DEFAULT is the minibuffer default argument."
+ (completing-read (format-prompt prompt default)
+ (kubed-contexts)
+ nil 'confirm nil 'kubed-context-history default))
+
+;;;###autoload
+(defun kubed-use-context (context)
+ "Set current Kubernetes context to CONTEXT."
+ (interactive
+ (list (kubed-read-context "Use context" (kubed-local-context))))
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil nil nil
+ "config" "use-context" context))
+ (user-error "Failed to use Kubernetes context `%s'" context))
+ (message "Now using Kubernetes context `%s'." context))
+
+;;;###autoload
+(defun kubed-rename-context (old new)
+ "Rename Kubernetes context OLD to NEW."
+ (interactive
+ (let ((old (kubed-read-context "Rename context" (kubed-local-context))))
+ (list old (read-string (format-prompt "Rename context to" old)
+ nil 'kubed-context-history old))))
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil nil nil
+ "config" "rename-context" old new))
+ (user-error "Failed to rename Kubernetes context `%s' to `%s'" old new))
+ (message "Renamed Kubernetes context `%s' to `%s'." old new))
+
+;;;###autoload
+(defun kubed-display-config ()
+ "Display current Kubernetes client settings in a YAML buffer."
+ (interactive)
+ (let* ((buf (get-buffer-create "*kubed-config*"))
+ (fun (lambda (&optional _ _)
+ (let ((inhibit-read-only t)
+ (target (current-buffer)))
+ (buffer-disable-undo)
+ (with-temp-buffer
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil t nil "config" "view"))
+ (error "`kubectl config view' failed"))
+ (let ((source (current-buffer)))
+ (with-current-buffer target
+ (replace-buffer-contents source)
+ (set-buffer-modified-p nil)
+ (buffer-enable-undo))))))))
+ (with-current-buffer buf
+ (funcall fun)
+ (goto-char (point-min))
+ (run-hooks 'kubed-yaml-setup-hook)
+ (setq-local revert-buffer-function fun))
+ (display-buffer buf)))
+
+(defun kubed-current-namespace (&optional context)
+ "Return current Kubernetes namespace for context CONTEXT.
+
+If no namespace is configured for CONTEXT, return nil."
+ (or (car (process-lines
+ kubed-kubectl-program
+ "config" "view" "-o"
+ (format "jsonpath={.contexts[?(.name==\"%s\")].context.namespace}"
+ (or context (kubed-current-context)))))))
+
+(defun kubed-local-namespace ()
+ "Return Kubernetes namespace in CONTEXT local to the current buffer."
+ (or kubed-list-namespace
+ (nth 3 kubed-display-resource-info)
+ (and (kubed-remote-file-name-p default-directory)
+ (kubed-tramp-namespace default-directory))
+ (kubed-default-namespace)))
+
+(defun kubed-local-context-and-namespace ()
+ "Return (CONTEXT . NAMESPACE) pair local to the current buffer."
+ (or (when-let ((context kubed-list-context))
+ (cons context
+ (or kubed-list-namespace
+ (kubed-current-namespace context))))
+ (when-let ((context (nth 2 kubed-display-resource-info)))
+ (cons context
+ (or (nth 3 kubed-display-resource-info)
+ (kubed-current-namespace context))))
+ (when-let ((context (and (kubed-remote-file-name-p default-directory)
+ (kubed-tramp-context default-directory))))
+ (cons context (kubed-tramp-namespace default-directory)))
+ (kubed-default-context-and-namespace)))
+
+;;;###autoload
+(defun kubed-set-namespace (namespace &optional context)
+ "Set default Kubernetes namespace in CONTEXT to NAMESPACE.
+
+Interactively, prompt for NAMESPACE and use the current context. With a
+prefix argument, prompt for CONTEXT as well."
+ (interactive
+ (let* ((context (kubed-local-context))
+ (context (if current-prefix-arg
+ (kubed-read-context "Context" context)
+ context)))
+ (list (kubed-read-namespace "Set namespace" (kubed--namespace context)
+ nil context)
+ context)))
+ (unless (zerop
+ (call-process
+ kubed-kubectl-program nil nil nil
+ "config" "set-context" (or context "--current")
+ "--namespace" namespace))
+ (user-error "Failed to set Kubernetes namespace to `%s'" namespace))
+ (message "Kubernetes namespace is now `%s'." namespace))
+
+(defcustom kubed-read-resource-definition-filter-files-by-kind t
+ "Whether to filter file completion candidates by their Kubernetes \"kind\".
+
+If this is non-nil, `kubed-read-resource-definition-file-name' only
+suggests files with the right \"kind\" as completion candidates when you
+call it with non-nil KIND argument. This is useful because you get more
+relevant completions, but it may become slow in directories with many
+large JSON and YAML files, in which case you can set this option to nil."
+ :type 'boolean)
+
+(defun kubed-read-resource-definition-file-name (&optional kind)
+ "Prompt for Kubernetes resource definition file name.
+
+Optional argument KIND is the kind of resource the file should define.
+If `kubed-read-resource-definition-filter-files-by-kind' is non-nil,
+this function suggests only files that define resources of kind KIND as
+completion candidates."
+ (read-file-name
+ (format "%s definition file: " (or kind "Resource")) nil nil t nil
+ (if (and kind kubed-read-resource-definition-filter-files-by-kind
+ (executable-find "grep"))
+ (let ((cache (make-hash-table :test 'equal)))
+ (lambda (f)
+ (or (file-directory-p f)
+ (when-let ((ext (and (string-match "\\.[^.]*\\'" f)
+ (substring f (1+ (match-beginning 0))))))
+ (or (and (member ext '("yaml" "yml"))
+ (pcase (gethash (expand-file-name f) cache 'noval)
+ ('noval
+ (puthash (expand-file-name f)
+ (zerop (call-process
+ "grep" f nil nil "-i"
+ (format "^kind: %s$" kind)))
+ cache))
+ (val val)))
+ (and (equal ext "json")
+ (pcase (gethash (expand-file-name f) cache 'noval)
+ ('noval
+ (puthash (expand-file-name f)
+ (zerop (call-process
+ "grep" f nil nil "-i"
+ (format "^kind: %s$" kind)))
+ cache))
+ (val val))))))))
+ (lambda (f)
+ (or (file-directory-p f)
+ (when (string-match "\\.[^.]*\\'" f)
+ (member (substring f (1+ (match-beginning 0)))
+ '("yaml" "yml" "json"))))))))
+
+;;;###autoload
+(defun kubed-apply (config &optional kind context)
+ "Apply CONFIG to Kubernetes resource of kind KIND via CONTEXT."
+ (interactive
+ (let ((config nil) (context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-apply))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--filename=\\(.+\\)" arg)
+ (setq config (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless config
+ (setq config (kubed-read-resource-definition-file-name)))
+ (list config nil context)))
+ (let ((kind (or kind "resource"))
+ (context (or context (kubed-local-context))))
+ (message "Applying Kubernetes %s configuration `%s'..." kind config)
+ (call-process kubed-kubectl-program nil nil nil
+ "apply" "-f" (expand-file-name config) "--context" context)
+ (message "Applying Kubernetes %s configuration `%s'... Done." kind config)))
+
+;;;###autoload
+(defun kubed-create (definition &optional kind context)
+ "Create resource of kind KIND with definition DEFINITION via CONTEXT."
+ (interactive
+ (let ((definition nil) (context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-create))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--filename=\\(.+\\)" arg)
+ (setq definition (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if current-prefix-arg
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless definition
+ (setq definition (kubed-read-resource-definition-file-name)))
+ (list definition nil context)))
+ (let ((kind (or kind "resource")))
+ (message "Creating Kubernetes %s with definition `%s'..." kind definition)
+ (message "Creating Kubernetes %s with definition `%s'... Done. New %s name is `%s'."
+ kind definition kind
+ (car (apply #'process-lines kubed-kubectl-program
+ "create" "-f" (expand-file-name definition)
+ "-o" "jsonpath={.metadata.name}"
+ (when context (list "--context" context)))))))
+
+;;;###autoload
+(defun kubed-run
+ ( pod image
+ &optional context namespace port attach stdin tty rm envs command args)
+ "Run container IMAGE in a new Kubernetes POD.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context. NAMESPACE is the namespace in which to create POD,
+defaulting to the current namespace. PORT is the port to expose,
+defaulting to none. If ATTACH is non-nil, then attach to the created
+image with a `comint-mode' buffer, and pop to that buffer. Non-nil
+STDIN says to keep the standard input of the container open; non-nil TTY
+says to allocate a TTY for the container; and non-nil RM says to remove
+the container after it exits. ENVS is a list of strings \"VAR=VAL\"
+which specify environment variables VAR and values VAL to give them in
+the created container. ARGS are command line arguments for the
+container command. If COMMAND is non-nil, ARGS consist of a complete
+command line, that overrides the container command instead of just
+providing it with arguments.
+
+Interactively, prompt for POD and IMAGE. With a prefix argument, prompt
+for NAMESPACE; with a double prefix argument, also prompt for CONTEXT."
+ (interactive
+ (let ((pod nil) (image nil) (port nil) (context nil) (namespace nil)
+ (attach nil) (stdin nil) (tty nil) (rm nil) (envs nil)
+ (command nil) (args nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-run))
+ (cond
+ ((string-match "--image=\\(.+\\)" arg)
+ (setq image (match-string 1 arg)))
+ ((string-match "--port=\\(.+\\)" arg)
+ (setq port (string-to-number (match-string 1 arg))))
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((equal "--attach" arg) (setq attach t))
+ ((equal "--stdin" arg) (setq stdin t))
+ ((equal "--tty" arg) (setq tty t))
+ ((equal "--rm" arg) (setq rm t))
+ ((equal "--command" arg) (setq command t))
+ ((string-match "--env=\\(.+\\)" arg)
+ (push (match-string 1 arg) envs))
+ ((string-match "-- =\\(.+\\)" arg)
+ (setq args (split-string-and-unquote (match-string 1 arg))))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (setq pod (read-string "Run image in pod with name: "))
+ (unless image
+ (setq image (read-string "Image to run: " nil 'kubed-container-image-history)))
+ (list pod image context namespace port attach stdin tty rm envs command args)))
+ (if attach
+ (pop-to-buffer
+ (apply #'make-comint "kubed-run" kubed-kubectl-program nil
+ "run" pod (concat "--image=" image) "--attach"
+ (append
+ (mapcar (lambda (env) (concat "--env=" env))
+ (cons "TERM=dumb" envs))
+ (when namespace (list (concat "--namespace=" namespace)))
+ (when stdin '("-i"))
+ (when tty '("-t"))
+ (when rm '("--rm"))
+ (when port (list (format "--port=%d" port)))
+ (when command '("--command"))
+ (when args (cons "--" args)))))
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "run" pod (concat "--image=" image)
+ (append
+ (mapcar (lambda (env) (concat "--env=" env)) envs)
+ (when namespace (list (concat "--namespace=" namespace)))
+ (when context (list (concat "--context=" context)))
+ (when stdin '("-i"))
+ (cond
+ (attach '("--attach"))
+ (stdin '("--attach=false")))
+ (when tty '("-t"))
+ (when rm '("--rm"))
+ (when port (list (format "--port=%d" port)))
+ (when command '("--command"))
+ (when args (cons "--" args)))))
+ (user-error "Failed to run image `%s'" image))
+ (message "Image `%s' is now running in pod `%s'." image pod)))
+
+(defun kubed-pod-containers (pod &optional context namespace)
+ "Return list of containers in Kubernetes pod POD in NAMESPACE in CONTEXT."
+ (string-split
+ (car (apply #'process-lines
+ kubed-kubectl-program "get"
+ "pod" pod "-o" "jsonpath={.spec.containers[*].name}"
+ (append
+ (when namespace (list "--namespace" namespace))
+ (when context (list "--context" context)))))
+ " "))
+
+(defun kubed-pod-default-container (pod &optional context namespace)
+ "Return default container of Kubernetes pod POD in NAMESPACE in CONTEXT."
+ (car (apply #'process-lines
+ kubed-kubectl-program
+ "get" "pod" pod "-o"
+ "jsonpath={.metadata.annotations.kubectl\\.kubernetes\\.io/default-container}"
+ (append
+ (when namespace (list "--namespace" namespace))
+ (when context (list "--context" context))))))
+
+(defun kubed-read-container
+ (pod prompt &optional guess context namespace)
+ "Prompt with PROMPT for a container in POD and return its name.
+
+Non-nil optional argument GUESS says to try and guess which container to
+use without prompting: if the pod has a
+\"kubectl.kubernetes.id/default-container\" annotation, use the
+container that this annotation specifes; if there's just one container,
+use it; otherwise, fall back to prompting."
+ (let ((default (kubed-pod-default-container pod context namespace))
+ (all 'unset))
+ (or
+ ;; There's a default container, so that's our guess.
+ (and guess default)
+ ;; No default, but we're allowed to guess, so check if there's just
+ ;; one container, and if so that's our guess.
+ (and guess (setq all (kubed-pod-containers pod context namespace))
+ (null (cdr all))
+ (car all))
+ ;; No guessing, prompt.
+ (completing-read (format-prompt prompt default)
+ (completion-table-dynamic
+ (lambda (_)
+ (if (eq all 'unset)
+ (setq all (kubed-pod-containers pod context namespace))
+ all)))
+ nil t nil nil default))))
+
+;;;###autoload
+(defun kubed-logs
+ ( type resource &optional context namespace
+ container follow limit prefix since tail timestamps)
+ "Show logs for container CONTAINER in Kubernetes RESOURCE of type TYPE.
+
+Optional argument CONTEXT is the `kubectl' context to use; NAMESPACE is
+the namespace for RESOURCE; CONTAINER is the name of the container in
+RESOURCE to show logs for, or t which specifies all relevant containers.
+
+With a prefix argument, prompt for NAMESPACE; with a double prefix
+argument, also prompt for CONTEXT."
+ (interactive
+ (let ( type resource context namespace
+ container follow limit prefix since tail timestamps)
+ (dolist (arg (kubed-transient-args 'kubed-transient-logs))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--limit-bytes=\\(.+\\)" arg)
+ (setq limit (string-to-number (match-string 1 arg))))
+ ((string-match "--tail=\\(.+\\)" arg)
+ (setq tail (string-to-number (match-string 1 arg))))
+ ((string-match "--since-time=\\(.+\\)" arg)
+ (setq since (match-string 1 arg)))
+ ((equal "--all-containers" arg) (setq container t))
+ ((equal "--follow" arg) (setq follow t))
+ ((equal "--prefix" arg) (setq prefix t))
+ ((equal "--timestamps" arg) (setq timestamps t))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (setq type (kubed-read-resource-type "Type of resource to display"
+ nil context))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (setq resource (kubed-read-resource-name type "Show logs for" nil nil
+ context namespace))
+ (list type resource context namespace
+ (or container
+ (and (string= type "pods")
+ (kubed-read-container resource "Container" t
+ context namespace)))
+ follow limit prefix since tail timestamps)))
+ (let* ((context (or context (kubed-local-context)))
+ (namespace (or namespace (kubed--namespace context)))
+ (buf (generate-new-buffer
+ (format "*kubed-logs %s/%s%s in %s[%s]*"
+ type resource
+ (cond
+ ((stringp container) (concat "[" container "]"))
+ (container "[all containers]")
+ (t ""))
+ namespace context))))
+ (with-current-buffer buf (run-hooks 'kubed-logs-setup-hook))
+ (apply #'start-process "*kubed-logs*" buf
+ kubed-kubectl-program "logs" (concat type "/" resource)
+ "--context" context "-n" namespace
+ (append
+ (cond
+ ((stringp container) (list "-c" container))
+ (container '("--all-containers")))
+ (when follow '("--follow"))
+ (when prefix '("--prefix"))
+ (when timestamps '("--timestamps"))
+ (when since (list "--since-time" since))
+ (when tail (list "--tail" (number-to-string tail)))
+ (when limit (list "--limit-bytes" (number-to-string limit)))))
+ (display-buffer buf)))
+
+(defvar kubed-port-forward-process-alist nil
+ "Alist of current port-forwarding descriptors and corresponding processes.")
+
+(defun kubed-port-forward-process-alist (&optional _ignored)
+ "Update and return value of variable `kubed-port-forward-process-alist'."
+ (setq kubed-port-forward-process-alist
+ (seq-filter (lambda (pair)
+ (process-live-p (cdr pair)))
+ kubed-port-forward-process-alist)))
+
+;;;###autoload
+(defun kubed-forward-port-to-pod
+ (pod local-port remote-port context namespace)
+ "Forward LOCAL-PORT to REMOTE-PORT of Kubernetes pod POD."
+ (interactive
+ (let* ((c (kubed-local-context))
+ (c (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" c)
+ c))
+ (n (kubed--namespace c current-prefix-arg))
+ (n (if current-prefix-arg
+ (kubed-read-namespace "Namespace" n nil c)
+ n))
+ (p (kubed-read-pod "Forward port to pod" nil nil c n))
+ (l (read-number "Local port: "))
+ (r (read-number "Remote port: ")))
+ (list p l r c n)))
+ (message "Forwarding local port %d to remote port %d of pod `%s' in namespace `%s'..."
+ local-port remote-port pod namespace)
+ (push
+ (cons
+ (format "%s %d:%d in %s[%s]" pod local-port remote-port namespace context)
+ (apply #'start-process
+ "*kubed-port-forward*" nil
+ kubed-kubectl-program "port-forward"
+ pod (format "%d:%d" local-port remote-port)
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context)))))
+ kubed-port-forward-process-alist))
+
+(defun kubed-stop-port-forward (descriptor)
+ "Stop Kubernetes port-forwarding with descriptor DESCRIPTOR.
+
+DESCRIPTOR is a string that says which port-forwarding process to stop,
+it has the format \"POD LOCAL-PORT:REMOTE-PORT in NAMESPACE[CONTEXT]\",
+where POD is the name of the pod that is the target of the
+port-forwarding, LOCAL-PORT is the local port that is being forwarded,
+REMOTE-PORT is the correspoding remote port of POD, NAMESPACE is the
+namespace of POD and CONTEXT is the `kubectl' context.
+
+Interactively, prompt for DESCRIPTOR with completion. If there is only
+one port-forwarding process, stop that process without prompting."
+ (interactive
+ (list
+ (cond
+ ((cdr (kubed-port-forward-process-alist))
+ (completing-read "Stop port-forwarding: "
+ (completion-table-dynamic
+ #'kubed-port-forward-process-alist)
+ nil t))
+ ((caar kubed-port-forward-process-alist))
+ (t (user-error "No port-forwarding to Kubernetes in progress")))))
+ (if-let ((pair (assoc descriptor kubed-port-forward-process-alist)))
+ (delete-process (cdr pair))
+ (error "No port-forwarding for %s" descriptor))
+ (message "Stopped port-forwarding for %s" descriptor))
+
+(defvar kubed-container-image-history nil
+ "Minibuffer history for `kubed-read-container-image'.")
+
+(defun kubed-read-container-image (prompt &optional default multi)
+ "Prompt with PROMPT for names of container images.
+
+Optional argument DEFAULT is the minibuffer default argument. Non-nil
+optional argument MULTI says to read multiple image names and return
+them as list."
+ (funcall (if multi #'completing-read-multiple #'completing-read)
+ (format-prompt prompt default) nil nil nil nil
+ 'kubed-container-images-history default))
+
+(defvar kubed-ingress-rule-history nil
+ "Minibuffer history for `kubed-read-ingress-rules'.")
+
+(defun kubed-read-ingress-rules ()
+ "Prompt with PROMPT for Kubernetes ingress rules."
+ (let ((rules (list (read-string "Ingress rule: "
+ nil 'kubed-ingress-rule-history))))
+ (while (not
+ (string-empty-p
+ (car (push (read-string (format-prompt "Additional rules" "done")
+ nil 'kubed-ingress-rule-history)
+ rules)))))
+ (nreverse (cdr rules))))
+
+(defvar transient-current-command)
+
+(defun kubed-transient-args (&optional prefix)
+ "Return current arguments from transient PREFIX.
+
+If PREFIX nil, it defaults to the value of `transient-current-command'."
+ (when-let ((prefix (or prefix (bound-and-true-p transient-current-command))))
+ (and (featurep 'kubed-transient)
+ (fboundp 'transient-args)
+ (transient-args prefix))))
+
+;;;###autoload
+(defun kubed-attach (pod &optional container context namespace stdin tty)
+ "Attach to running process in CONTAINER in Kubernetes POD.
+
+Optional argument NAMESPACE is the namespace in which to look for POD.
+Non-nil STDIN says to connect local standard input to remote process.
+Non-nil TTY says to use a TTY for standard input.
+
+Interactively, prompt for POD; if there are multiple pod containers,
+prompt for CONTAINER as well. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+STDIN and TTY are t interactively."
+ (interactive
+ (let ((context nil) (namespace nil) (stdin t) (tty t))
+ (dolist (arg (kubed-transient-args 'kubed-transient-attach))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((equal "--stdin" arg) (setq stdin t))
+ ((equal "--tty" arg) (setq tty t))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (let* ((p (kubed-read-pod "Exec on pod" nil nil context namespace))
+ (c (kubed-read-container p "Container" t context namespace)))
+ (list p c context namespace stdin tty))))
+ (pop-to-buffer
+ (apply #'make-comint "kubed-attach" kubed-kubectl-program nil
+ "attach" pod
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ (when container (list "-c" container))
+ (when stdin '("-i"))
+ (when tty '("-t"))))))
+
+;;;###autoload
+(defun kubed-diff (definition &optional include-managed context)
+ "Display difference between Kubernetes resource DEFINITION and current state.
+
+DEFINITION is either a file name or a buffer. Interactively, prompt for
+a YAML or JSON file name to use as DEFINITION.
+
+Optional argument CONTEXT is the `kubectl' context to use, defaulting to
+the current context; non-nil INCLUDE-MANAGED (interactively, the prefix
+argument) says to include managed fields in the comparison."
+ (interactive
+ (let ((definition nil) (context nil) (include-managed nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-diff))
+ (cond
+ ((string-match "--filename=\\(.+\\)" arg)
+ (setq definition (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((equal "--show-managed-fields" arg) (setq include-managed t))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list (or definition (kubed-read-resource-definition-file-name))
+ (or include-managed current-prefix-arg)
+ context)))
+ (let ((buf (get-buffer-create "*kubed-diff*"))
+ (args (cons "diff"
+ (append
+ (when context (list "--context" context))
+ (when include-managed
+ (list "--show-managed-fields" "true"))
+ (list "-f" (if (bufferp definition) "-"
+ (expand-file-name definition)))))))
+ (with-current-buffer buf
+ (setq buffer-read-only nil)
+ (delete-region (point-min) (point-max))
+ (fundamental-mode)
+ (if (bufferp definition)
+ (with-current-buffer definition
+ (apply #'call-process-region nil nil kubed-kubectl-program
+ nil buf nil args))
+ (apply #'call-process kubed-kubectl-program
+ nil t nil args))
+ (setq buffer-read-only t)
+ (diff-mode)
+ (goto-char (point-min)))
+ (display-buffer buf)))
+
+;;;###autoload
+(defun kubed-exec
+ (pod command &optional container context namespace stdin tty args)
+ "Execute COMMAND with ARGS in CONTAINER in Kubernetes POD.
+
+Optional argument NAMESPACE is the namespace in which to look for POD.
+Non-nil STDIN says to connect local standard input to remote process.
+Non-nil TTY says to use a TTY for standard input.
+
+Interactively, prompt for POD; if there are multiple pod containers,
+prompt for CONTAINER as well. With a prefix argument, prompt for
+NAMESPACE too. With a double prefix argument, also prompt for CONTEXT.
+STDIN and TTY are t interactively."
+ (interactive
+ (let ((context nil) (namespace nil) (stdin t) (tty t) (command nil) (args nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-exec))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((equal "--stdin" arg) (setq stdin t))
+ ((equal "--tty" arg) (setq tty t))
+ ((string-match "-- =\\(.+\\)" arg)
+ (setq args (split-string-and-unquote (match-string 1 arg))
+ command (car args)
+ args (cdr args)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (unless command
+ (setq args (split-string-and-unquote
+ (read-string "Execute command: "))
+ command (car args)
+ args (cdr args)))
+ (let* ((p (kubed-read-pod "Exec on pod" nil nil context namespace))
+ (c (kubed-read-container p "Container" t context namespace)))
+ (list p command c context namespace stdin tty args))))
+ (pop-to-buffer
+ (apply #'make-comint "kubed-exec" kubed-kubectl-program nil
+ "exec" pod
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ (when container (list "-c" container))
+ (when stdin '("-i"))
+ (when tty '("-t"))
+ (list "--" command)
+ args))))
+
+(defvar kubed-patch-history nil
+ "Minibuffer history for `kubed-read-patch'.")
+
+(defun kubed-read-patch ()
+ "Prompt for a Kubernetes resource patch in JSON or YAML format."
+ (read-string "Patch (JSON or YAML): " nil 'kubed-patch-history))
+
+;;;###autoload
+(defun kubed-patch (type name patch &optional context namespace strategy)
+ "Patch Kubernetes resource NAME of TYPE with patch PATCH.
+
+Optional argument NAMESPACE is the namespace in which to look for NAME.
+STRATEGY is the patch type to use, one of \"json\", \"merge\" and
+\"strategic\", defaulting to \"strategic\".
+
+Interactively, prompt for TYPE, NAME and PATCH. With a prefix argument,
+prompt for NAMESPACE too. With a double prefix argument, also prompt
+for CONTEXT."
+ (interactive
+ (let ((type nil) (context nil) (namespace nil) (strategy nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-patch))
+ (cond
+ ((string-match "--namespace=\\(.+\\)" arg)
+ (setq namespace (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))
+ ((string-match "--type=\\(.+\\)" arg)
+ (setq strategy (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (unless namespace
+ (setq namespace (kubed--namespace context current-prefix-arg)))
+ (list (kubed-read-resource-type "Resource type to patch" nil context)
+ (kubed-read-resource-name type "Resource to patch" nil nil context namespace)
+ (kubed-read-patch) context namespace strategy)))
+ (message "Applying patch to `%s'..." name)
+ (unless (zerop
+ (apply #'call-process
+ kubed-kubectl-program nil nil nil
+ "patch" type name "-p" patch
+ (append
+ (when namespace (list "-n" namespace))
+ (when context (list "--context" context))
+ (when strategy (list "--type" strategy)))))
+ (user-error "Patching `%s' failed" name))
+ (message "Applying patch to `%s'... Done." name))
+
+(defvar kubed--proxy-alist nil
+ "Alist associating `kubectl' context names and their proxy processes.")
+
+;;;###autoload
+(defun kubed-proxy
+ (&optional addr port api-prefix www-dir www-prefix context)
+ "Start a web server on localhost proxying the Kubernetes API server.
+
+ADDR and PORT are the local address and port to bind the proxy server
+to. They default to 127.0.0.1 and 8001, respectively. API-PREFIX is a
+prefix under which to proxy the Kubernetes API. WWW-DIR is a local
+directory to serve under WWW-PREFIX, which defaults to \"/static/\".
+Lastly, CONTEXT is the the `kubectl' context to use, defaulting to the
+local context."
+ (interactive
+ (let ((addr nil) (port nil)
+ (api-prefix nil) (www-dir nil) (www-prefix nil)
+ (context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-proxy))
+ (cond
+ ((string-match "--address=\\(.+\\)" arg)
+ (setq addr (match-string 1 arg)))
+ ((string-match "--port=\\(.+\\)" arg)
+ (setq port (string-to-number (match-string 1 arg))))
+ ((string-match "--api-prefix=\\(.+\\)" arg)
+ (setq api-prefix (match-string 1 arg)))
+ ((string-match "--www-dir=\\(.+\\)" arg)
+ (setq www-dir (match-string 1 arg)))
+ ((string-match "--www-prefix=\\(.+\\)" arg)
+ (setq www-prefix (match-string 1 arg)))
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list addr port api-prefix www-dir www-prefix context)))
+ (let ((context (or context (kubed-local-context))))
+ (when-let ((proc (alist-get context kubed--proxy-alist
+ nil nil #'string=))
+ ((process-live-p proc)))
+ (if (y-or-n-p (concat "Proxy already running for context `" context
+ "'. Stop it and start new proxy?"))
+ (kill-process proc)
+ (user-error "Proxy already running for context `%s'" context))
+ (message "Stopped proxy for context `%s'." context))
+ (setf (alist-get context kubed--proxy-alist nil nil #'string=)
+ (apply #'start-process (format "*kubed-proxy[%s]*" context) nil
+ kubed-kubectl-program "proxy"
+ "--context" context
+ (append
+ (when addr (list "--address" addr))
+ (when port (list "--port" (number-to-string port)))
+ (when api-prefix (list "--api-prefix" api-prefix))
+ (when www-dir (list "--www-dir" www-dir))
+ (when www-prefix (list "--www-prefix" www-prefix)))))
+ (message "Started proxy for context `%s'." context)))
+
+;;;###autoload
+(defun kubed-stop-proxy (&optional context)
+ "Stop local proxy for context CONTEXT, defaulting to the local context."
+ (interactive
+ (let ((context nil))
+ (dolist (arg (kubed-transient-args 'kubed-transient-proxy))
+ (cond
+ ((string-match "--context=\\(.+\\)" arg)
+ (setq context (match-string 1 arg)))))
+ (unless context
+ (setq context
+ (let ((cxt (kubed-local-context)))
+ (if (equal current-prefix-arg '(16))
+ (kubed-read-context "Context" cxt)
+ cxt))))
+ (list context)))
+ (let ((context (or context (kubed-local-context))))
+ (if-let ((proc (alist-get context kubed--proxy-alist
+ nil nil #'string=))
+ ((process-live-p proc)))
+ (kill-process proc)
+ (user-error "No proxy running for context `%s'" context))
+ (message "Stopped proxy for context `%s'." context)))
+
+(with-eval-after-load 'help-mode
+ ;; Wait for `help-mode' to define `help-xref'. It's always loaded by
+ ;; the time we actually need it in `kubed-explain'.
+ (define-button-type 'kubed-explain
+ :supertype 'help-xref
+ 'help-function 'kubed-explain
+ 'help-echo "mouse-2, RET: explain"))
+
+(defvar kubed-resource-field-history nil
+ "Minibuffer history for `kubed-read-resource-field'.")
+
+(defun kubed-api-resources (&optional context only-namespaced)
+ "Return list of resource types in the context CONTEXT.
+
+Non-nil optional argument ONLY-NAMESPACED says to return only namespaced
+resource types."
+ (mapcar
+ (lambda (line)
+ (car (split-string line)))
+ (apply #'process-lines
+ kubed-kubectl-program
+ "api-resources" "--no-headers"
+ (append
+ (when only-namespaced '("--namespaced=true"))
+ (when context (list (concat "--context=" context)))))))
+
+(defun kubed-resource-names (type context &optional namespace)
+ "Return list of Kubernetes resources of type TYPE in NAMESPACE via CONTEXT."
+ (unless (kubed--alist type context namespace)
+ (let ((proc (kubed-update type context namespace)))
+ (while (process-live-p proc)
+ (accept-process-output proc 1))))
+ (mapcar #'car (alist-get 'resources (kubed--alist type context namespace))))
+
+(defun kubed-read-resource-name (type prompt &optional default multi context namespace)
+ "Prompt with PROMPT for Kubernetes resource name of type TYPE.
+
+Optional argument DEFAULT is the minibuffer default argument; non-nil
+MULTI says to read multiple names and return them as a list; CONTEXT is
+the `kubectl' context to use, defaulting to the local context; and
+NAMESPACE is the namespace to use, or nil if TYPE is non-namespaced."
+ (funcall
+ (if multi #'completing-read-multiple #'completing-read)
+ (format-prompt prompt default)
+ (let ((table 'unset))
+ (lambda (s p a)
+ (if (eq a 'metadata)
+ `(metadata (category . ,(intern (concat "kubernetes-" type))))
+ (when (eq table 'unset)
+ (setq table (kubed-resource-names
+ type (or context (kubed-local-context)) namespace)))
+ (complete-with-action a table s p))))
+ nil 'confirm nil (intern (concat "kubed-" type "-history")) default))
+
+(defun kubed-read-resource-type (prompt &optional default context)
+ "Prompt with PROMPT for Kubernetes resource type in context CONTEXT.
+
+Optional argument DEFAULT is the minibuffer default argument."
+ (completing-read
+ (format-prompt prompt default)
+ (kubed-api-resources context)
+ nil 'confirm nil nil default))
+
+(defun kubed-read-resource-field (prompt &optional default context)
+ "Prompt with PROMPT for Kubernetes resource type or field name.
+
+Optional argument DEFAULT is the minibuffer default argument, and
+CONTEXT is the `kubectl' context to use."
+ (completing-read
+ (format-prompt prompt default)
+ (lambda (s p a)
+ (unless (eq a 'metadata)
+ (let ((start 0))
+ (while (string-match "\\." s start)
+ (setq start (match-end 0)))
+ (if (eq (car-safe a) 'boundaries)
+ `(boundaries ,start . ,(and (string-match "\\." (cdr a))
+ (match-beginning 0)))
+ (let ((table
+ (if (zerop start)
+ ;; Complete resource type.
+ (kubed-api-resources context)
+ ;; Complete (sub-)field name.
+ (with-temp-buffer
+ (call-process
+ kubed-kubectl-program nil t nil
+ "explain" (substring s 0 start))
+ (goto-char (point-min))
+ (let ((res nil))
+ (while (re-search-forward
+ (rx line-start (+ " ") (group-n 1 (* alnum)) "\t")
+ nil t)
+ (push (match-string 1) res))
+ res)))))
+ (if a (complete-with-action a table (substring s start) p)
+ ;; `try-completion'.
+ (let ((comp (complete-with-action a table (substring s start) p)))
+ (if (stringp comp) (concat (substring s 0 start) comp) comp))))))))
+ nil 'confirm nil 'kubed-resource-field-history default))
+
+;;;###autoload
+(defun kubed-explain (field)
+ "Show help buffer with explanation about Kubernetes resource FIELD."
+ (interactive
+ (list (kubed-read-resource-field "Explain type or field")))
+ (let ((help-buffer-under-preparation t))
+ (help-setup-xref (list #'kubed-explain field)
+ (called-interactively-p 'interactive))
+ (with-help-window (help-buffer)
+ (with-current-buffer (help-buffer)
+ (insert (substitute-quotes
+ (concat "`kubed explain " field "' says:\n\n")))
+ (save-excursion
+ ;; Add button that goes to parent.
+ (goto-char (point-min))
+ (when (re-search-forward (rx " "
+ (group-n 1 (* graph))
+ "."
+ (+ (not (any ?.)))
+ line-end)
+ nil t)
+ (help-xref-button 1 'kubed-explain (match-string 1))))
+
+ (call-process kubed-kubectl-program nil t nil "explain" field)
+ ;; Buttonize references to other fields.
+ (goto-char (point-min))
+ (while (re-search-forward (rx line-start
+ (+ " ")
+ (group-n 1 (* alnum))
+ "\t")
+ nil t)
+ (help-xref-button 1 'kubed-explain
+ (concat field "." (match-string 1))))))))
+
+(defvar kubed-kubectl-command-history nil
+ "Minibuffer history for `kubed-kubectl-command'.")
+
+(declare-function cobra-read-command-line "cobra"
+ (prompt initial &optional hist))
+
+(defun kubed-read-kubectl-command (prompt &optional initial)
+ "Prompt with PROMPT for a `kubectl' command line.
+
+Optional argument INITIAL added to the initial minibuffer input
+following the value of `kubed-kubectl-program' and a space character."
+ (let ((init (if (consp initial)
+ (cons (concat kubed-kubectl-program " " (car initial))
+ (+ (length kubed-kubectl-program) 1 (cdr initial)))
+ (concat kubed-kubectl-program " " initial))))
+ (cobra-read-command-line prompt init 'kubed-kubectl-command-history)))
+
+;;;###autoload
+(defun kubed-kubectl-command (command)
+ "Execute `kubectl' COMMAND.
+
+This function calls `shell-command' (which see) to do the work.
+
+Interactively, prompt for COMMAND with completion for `kubectl' arguments."
+ (interactive
+ (list (kubed-read-kubectl-command
+ "Command: "
+ (let* ((args (kubed-transient-args))
+ (prefix (and (fboundp 'transient-prefix-object)
+ (transient-prefix-object)))
+ (scope (and prefix (fboundp 'eieio-oref)
+ (eieio-oref prefix 'scope))))
+ (when (or args scope)
+ (concat (string-join (append scope args) " ") " "))))))
+ (shell-command command))
+
+;;;###autoload (autoload 'kubed-prefix-map "kubed" nil t 'keymap)
+(defvar-keymap kubed-prefix-map
+ :doc "Prefix keymap for Kubed commands."
+ :prefix 'kubed-prefix-map
+ "p" 'kubed-pod-prefix-map
+ "N" 'kubed-namespace-prefix-map
+ "s" 'kubed-service-prefix-map
+ "S" 'kubed-secret-prefix-map
+ "j" 'kubed-job-prefix-map
+ "d" 'kubed-deployment-prefix-map
+ "i" 'kubed-ingress-prefix-map
+ "c" 'kubed-cronjob-prefix-map
+ "C" #'kubed-use-context
+ "+" #'kubed-create
+ "*" #'kubed-apply
+ "R" #'kubed-run
+ "=" #'kubed-diff
+ "E" #'kubed-explain
+ "P" #'kubed-patch
+ ":" #'kubed-proxy
+ "RET" #'kubed-display-resource
+ "!" #'kubed-kubectl-command)
+
+(defvar-keymap kubed-menu-map
+ :doc "Keymap with Kubed menu entries."
+ :prefix 'kubed-menu-map
+ "<namespace>" '("Namespaces..." . kubed-namespace-menu-map)
+ "<pod>" '("Pods..." . kubed-pod-menu-map)
+ "<persistentvolume>" '("Persistent Volumes..." . kubed-persistentvolume-menu-map)
+ "<service>" '("Services..." . kubed-service-menu-map)
+ "<secret>" '("Secrets..." . kubed-secret-menu-map)
+ "<job>" '("Jobs..." . kubed-job-menu-map)
+ "<deployment>" '("Deployments..." . kubed-deployment-menu-map)
+ "<replicaset>" '("Replica Sets..." . kubed-replicaset-menu-map)
+ "<statefulset>" '("Stateful Sets..." . kubed-statefulset-menu-map)
+ "<cronjob>" '("Cron Jobs..." . kubed-cronjob-menu-map)
+ "<ingressclass>" '("Ingress Classes..." . kubed-ingressclass-menu-map)
+ "<ingress>" '("Ingresses..." . kubed-ingress-menu-map)
+ "<proxy>" '("Proxy Kubernetes API" . kubed-proxy)
+ "<patch>" '("Patch Resource" . kubed-patch)
+ "<diff>" '("Diff Config with Live" . kubed-diff)
+ "<kubectl-command>" '("Invoke kubectl" . kubed-kubectl-command)
+ "<explain>" '("Explain Type or Field" . kubed-explain)
+ "<use-context>" '("Set Current Context" . kubed-use-context)
+ "<run>" '("Run Image" . kubed-run)
+ "<apply>" '("Apply Config" . kubed-apply)
+ "<create>" '("Create Resource" . kubed-create)
+ "<display>" '("Display Resource" . kubed-display-resource))
+
+;;;###autoload
+(define-minor-mode kubed-menu-bar-mode
+ "Add \"Kubernetes\" menu to your menu bar."
+ :global t
+ (if kubed-menu-bar-mode
+ (keymap-set-after (current-global-map)
+ "<menu-bar> <kubed>" '("Kubernetes" . kubed-menu-map))
+ (keymap-global-unset "<menu-bar> <kubed>")))
+
+(defvar reporter-prompt-for-summary-p)
+
+(defun kubed-submit-bug-report ()
+ "Report a Kubed issue to the maintainers via email."
+ (interactive)
+ (require 'reporter)
+ (let ((reporter-prompt-for-summary-p t))
+ (reporter-submit-bug-report
+ "Kubed Development <~eshel/kubed-develNOSPAMlists.sr.ht>"
+ (format "Kubed")
+ '(kubed-kubectl-program)
+ nil nil
+ (propertize " "
+ 'display
+ (propertize
+ "Insert your bug report below.
+If possible, specify where you got Emacs, kubectl and Kubed,
+and include a recipe for reproducing your issue.
+[This line and the above text are not included in your report.]"
+ 'face 'italic)))))
+
+(provide 'kubed)
+;;; kubed.el ends here
--- /dev/null
+This is doc5HCYE0.info, produced by makeinfo version 6.8 from
+kubed.texi.
+
+This manual is for Kubed 0.4.2, a rich Emacs interface for Kubernetes.
+
+ Copyright © 2024 Eshel Yaron.
+
+ Permission is granted to copy, distribute and/or modify this
+ document under the terms of the GNU Free Documentation License,
+ Version 1.3 or any later version published by the Free Software
+ Foundation; with no Invariant Sections, no Front-Cover Texts, and
+ no Back-Cover Texts.
+INFO-DIR-SECTION Emacs
+START-INFO-DIR-ENTRY
+* Kubed: (kubed). Kubernetes, Emacs, done!
+END-INFO-DIR-ENTRY
+
+\1f
+File: doc5HCYE0.info, Node: Top, Next: Overview, Up: (dir)
+
+Kubed: Kubernetes, Emacs, done!
+*******************************
+
+This manual is for Kubed 0.4.2, a rich Emacs interface for Kubernetes.
+
+* Menu:
+
+* Overview:: Introduction to Kubed
+* Getting Started:: First steps with Kubed
+* Usage:: Using Kubed to interact with Kubernetes
+* Extending Kubed:: Adding support for your custom resources
+* Contributing:: Get involved in Kubed development
+* Indices::
+
+\1f
+File: doc5HCYE0.info, Node: Overview, Next: Getting Started, Prev: Top, Up: Top
+
+1 Overview
+**********
+
+Kubed is a rich Kubernetes interface within Emacs. It helps you work
+with your Kubernetes clusters and deployments with the full power of
+‘kubectl’, and with the comfort and confidence of an intuitive
+interactive interface.
+
+ You can use Kubed to:
+
+ • Browse and manage Kubernetes workloads
+ • Connect to pods and edit files or execute commands
+ • Create new resources, edit and delete them
+ • Work with multiple clusters
+ • Get help about various Kubernetes objects
+ • ...
+
+ These features and others are documented in the rest of this manual,
+along with many options that Kubed provides for you to customize its
+behavior.
+
+\1f
+File: doc5HCYE0.info, Node: Getting Started, Next: Usage, Prev: Overview, Up: Top
+
+2 Getting Started
+*****************
+
+Get Kubed from GNU ELPA with ‘M-x package-install’, or use your favorite
+Emacs package manager to install Kubed from Git. You can clone the
+Kubed Git repository from any of the following locations:
+
+ • <https://git.sr.ht/~eshel/kubed>
+ • <https://github.com/eshelyaron/kubed.git>
+ • <git://git.eshelyaron.com/kubed.git>
+
+ To get started with Kubed, all you need is ‘kubectl’ and Emacs.
+
+ Kubed tries to find ‘kubectl’ in the directories listed in the Emacs
+variable ‘exec-path’. When Emacs is started from a shell, it
+initializes ‘exec-path’ from the shell’s ‘PATH’ environment variable
+which normally includes the location of ‘kubectl’ in common ‘kubectl’
+installations. If Emacs doesn’t find the ‘kubectl’ executable via
+‘exec-path’, you can tell Kubed where to find it by customizing
+‘kubed-kubectl-program’.
+
+ -- User Option: kubed-kubectl-program
+ Name of ‘kubectl’ executable file to use.
+
+\1f
+File: doc5HCYE0.info, Node: Usage, Next: Extending Kubed, Prev: Getting Started, Up: Top
+
+3 Usage
+*******
+
+Kubed comes with a large number of Emacs command for interacting with
+Kubernetes. Some commands perform one action on one resource, while
+others show you a menu with many resources and let you interact with any
+number of them. Some commands are specific to one type of resources,
+while others act on resources of any type. There are different ways to
+use Kubed, depending on your use case.
+
+ The two most important entry points for Kubed are ‘kubed-prefix-map’
+and ‘kubed-transient’:
+
+ -- Keymap: kubed-prefix-map
+ Keymap with bindings for many useful Kubed commands.
+
+ The prefix keymap ‘kubed-prefix-map’ gives you quick access to the
+main Kubed commands. You may want to bind it to a convenient key in
+your global keymap, for example:
+
+ (keymap-global-set "C-c k" 'kubed-prefix-map)
+
+ With this binding in place, you can create a new Kubernetes resource
+with ‘C-c k +’ and attach to a running pod with ‘C-c k p a’. The ‘C-c
+k’ binding is just a suggestion of course, feel free to pick any key
+sequence you like.
+
+ You can also access all of the commands in ‘kubed-prefix-map’ via the
+menu-bar ‘Kubernetes’ menu—to add this menu to your menu-bar, enable the
+global minor mode ‘kubed-menu-bar-mode’:
+
+ -- Minor Mode: kubed-menu-bar-mode
+ Add ‘Kubernetes’ menu to your menu bar.
+
+ The other important entry point that Kubed provides for exploring and
+performing different Kubernetes operations is ‘kubed-transient’:
+
+ -- Command: kubed-transient
+ Open the main Kubed transient menu.
+
+ This command pops up the Kubed transient menu, which gives you
+interactive guidance for invoking Kubed commands. *Note
+(transient)Top::.
+
+ The following sections describe in detail the various Kubed commands.
+
+* Menu:
+
+* Display Resource::
+* Browse Resources::
+* Context and Namespace::
+* Transient Menus::
+* Scale Deployment::
+
+\1f
+File: doc5HCYE0.info, Node: Display Resource, Next: Browse Resources, Up: Usage
+
+3.1 Display Resource
+====================
+
+You can view the YAML description of any resource in your Kubernetes
+cluster. In Kubed, this is referred to as “displaying” the given
+resource. The following commands display a Kubernetes resource:
+
+ -- Command: kubed-display-resource
+ Display a Kubernetes resource.
+
+ -- Command: kubed-display-pod
+ -- Command: kubed-display-deployment
+ -- Command: kubed-display-namespace
+ -- Command: kubed-display-FOO
+ Display Kubernetes resource of a given type.
+
+ Command ‘kubed-display-resource’ can display any Kubernetes resource.
+It prompts you to select a resource type and a resource name, and
+displays that resource in a dedicated buffer. By default, this command
+assumes you want to display a resource that is in the current namespace,
+but if you invoke ‘kubed-display-resource’ with a prefix argument
+(‘C-u’) and choose a namespaced resource type, then it also lets you
+select the namespace of the resource. With a double prefix argument, it
+lets you select which ‘kubectl’ context to use too. *Note Context and
+Namespace::.
+
+ If you have ‘kubed-prefix-map’ bound to ‘C-c k’, you can invoke
+‘kubed-display-resource’ with ‘C-c k <RET>’. This command is also
+available from the menu-bar “Kubernetes” menu (*note Definition of
+kubed-menu-bar-mode::), and from the transient “Kubernetes Display” menu
+(*note Transient Menus::).
+
+ You can also display the resource at point in resources list buffers
+by simply typing ‘<RET>’. *Note Browse Resources::.
+
+ Kubed also provides a dedicated ‘kubed-display-FOO’ command for each
+supported resource type FOO. These commands are just like
+‘kubed-display-resource’, except that they do not prompt you for a type,
+since they are type-specific. For example, ‘kubed-display-pod’ only
+prompts you for a pod name, and then displays it like
+‘kubed-display-resource’ does.
+
+ You can invoke these commands by adding a type-specific key before
+‘<RET>’ in ‘C-c k <RET>’: ‘C-c k p <RET>’ invokes ‘kubed-display-pod’,
+‘C-c k d <RET>’ invokes ‘kubed-display-deployment’, and so on.
+
+ When Kubed prepares a buffer for displaying a resource, it runs the
+hook ‘kubed-yaml-setup-hook’ right after populating the buffer with the
+YAML description of the resource:
+
+ -- User Option: kubed-yaml-setup-hook
+ List of functions to call, in order, to prepare YAML buffers. The
+ first function on this hook is normally the major mode you want to
+ use for YAML buffers that display Kubernetes resources. By
+ default, this is the list ‘(yaml-ts-mode view-mode)’.
+
+ In addition, Kubed enables a bespoke minor mode
+‘kubed-display-resource-mode’ in buffers that display Kubernetes
+resources:
+
+ -- Minor Mode: kubed-display-resource-mode
+ Minor mode for buffers that display a Kubernetes resource.
+
+This minor mode does the following:
+
+ • It shows the word ‘Kubed’ in the mode line, so you know you’re
+ seeing a rendered Kubernetes resource, rather than any old YAML
+ file.
+ • It allows you to _bookmark_ the resource your viewing and your
+ position in its YAML description with the regular Emacs bookmarks
+ commands (*note (emacs)Bookmarks::), namely ‘C-x r m’.
+ • It binds the key sequence ‘C-c C-j’ to command
+ ‘kubed-display-resource-jump-to-list’, which pops up the resources
+ list buffer for the type of the displayed resource, and takes you
+ to the line corresponding to it.
+ • It binds ‘C-c C-c’ to command ‘kubed-display-resource-replace’,
+ which lets you apply any changes that you make in the YAML buffer
+ back to the resource. When you hit ‘C-c C-c’, Kubed asks you for
+ confirmation before proceeding; you can reply with ‘d’ (‘diff’) to
+ show a diff between the current state of the resource and the
+ current buffer contents. You can also display this diff anytime
+ with ‘C-c C-=’. Note that by default Kubed displays resources in
+ read-only buffers, so you may need to hit ‘C-x C-q’
+ (‘read-only-mode’) before making changes in the buffer.
+ • It lets you repopulate the buffer with updated information about
+ the resource with ‘C-x x g’ (‘revert-buffer-quick’) and
+ ‘M-x revert-buffer’. This is also useful if you edit the resource
+ and want to restart from a clean slate.
+
+\1f
+File: doc5HCYE0.info, Node: Browse Resources, Next: Context and Namespace, Prev: Display Resource, Up: Usage
+
+3.2 Browse Resources
+====================
+
+Kubed gives you an interactive list of resources for each type, that you
+access with the ‘kubed-list-RESOURCEs’ commands, such as
+‘kubed-list-pods’.
+
+ These commands are bound to ‘l’ in Kubed’s type-specific prefix
+keymaps, most of which are accessible via the global ‘kubed-prefix-map’
+by default (*note Definition of kubed-prefix-map::). So if you’ve bound
+‘kubed-prefix-map’ to ‘C-c k’, you can pop up the pods list with ‘C-c k
+p l’, the deployments list with ‘C-c k d l’, the jobs list with
+‘C-c k j l’, etc. Normally, Kubed shows the list of resources in the
+current namespace, but if you invoke these commands with a prefix
+argument (say, ‘C-u C-c k p l’), they first prompt you to select a
+namespace, and then show resources in the namespace you selected.
+
+ The list of RESOURCEs in shown a buffer called
+‘*Kubed RESOURCEs@NAMESPACE[CONTEXT]*’, where CONTEXT is a ‘kubectl’
+context, and NAMESPACE is a namespace in CONTEXT. For example, Kubed
+shows the list of pods in namespace ‘default’ for context ‘minikube’ in
+buffer ‘*Kubed pos@default[minikube]’. For non-namespaced resources,
+the ‘@NAMESPACE’ part is omitted.
+
+ Each resource type has its own major mode for list buffers with
+type-specific commands and operations, but they are all also derived
+from a shared parent mode. The pods list uses the ‘kubed-pods-mode’
+(‘Kubernetes Pods’) major mode, the list of Kubernetes deployments uses
+‘kubed-deployments-mode’, and so on.
+
+ The common parent major mode is ‘kubed-list-mode’:
+
+ -- Major Mode: kubed-list-mode
+ Major mode for listing generic Kubernetes resources.
+
+ This mode provides the following commands for all type-specific
+derived modes:
+
+‘<?>’
+ Show help in transient menu (‘kubed-list-transient’).
+‘<RET>’
+ Display the resource at point in the current window
+ (‘kubed-list-select-resource’).
+‘o’
+ Display the resource at point in another window and select that
+ window (‘kubed-list-select-resource-other-window’).
+‘C-o’
+ Display the resource at point in another window without selecting
+ it (‘kubed-list-display-resource’).
+‘D’
+ Delete the resource at point (‘kubed-list-delete’).
+‘e’
+ Edit the resource at point (‘kubed-list-edit’).
+‘P’
+ Patch the resource at point (‘kubed-list-patch’).
+‘g’
+ Update the resources list by fetching new information from
+ Kubernetes (‘kubed-list-update’).
+‘/’
+ Filter the current list of resources (‘kubed-list-set-filter’).
+ *Note List Filter::.
+‘d’
+ Mark the resource at point for deletion
+ (‘kubed-list-mark-for-deletion’). Resources marked for deletion
+ have a ‘D’ in the beginning of their line.
+‘x’
+ Delete marked resources (‘kubed-list-delete-marked’).
+‘u’
+ Unmark from resource at point (‘kubed-list-unmark’).
+‘w’
+ Copy name of resource at point into the kill ring
+ (‘kubed-list-copy-as-kill’).
+‘n’
+ Move point to the next line (‘next-line’).
+‘p’
+ Move point to the previous line (‘previous-line’).
+‘<TAB>’
+ Move point to the next column. If point is already at the last
+ column, move to the first column of the next line instead
+ (‘kubed-list-next-column’).
+‘S-<TAB>’
+ Move point to the previous column. If point is already at the
+ first column, move to the last column of the previous line instead
+ (‘kubed-list-previous-column’).
+‘S’
+ Sort lines by the column at point (‘tabulated-list-sort’).
+‘|’
+ Fit width of column at point to its content
+ (‘kubed-list-fit-column-width-to-content’). With non-negative
+ prefix argument N (for example, ‘0 |’), fit Nth column. With
+ negative prefix argument (‘- |’), fit all columns.
+‘{’
+ Narrow the column at point
+ (‘tabulated-list-narrow-current-column’).
+‘}’
+ Widen the column at point (‘tabulated-list-widen-current-column’).
+
+ In addition, you can bookmark any Kubed resources list buffer with
+the usual ‘C-x r m’. *Note (emacs)Bookmarks::.
+
+ Note that ‘kubed-list-mode’ (and its type-specific derivatives) bind
+the ‘g’ key, which is commonly bound to ‘revert-buffer’, to
+‘kubed-list-update’. The difference between these commands is that
+‘revert-buffer’ only refreshes the list display, without fetching
+updated data from Kubernetes. You can still invoke ‘revert-buffer’ with
+‘C-x x g’ or ‘M-x’, if you need it.
+
+ Whenever Kubed is fetching data from Kubernetes to update a resources
+list buffer, the mode line of that list buffer shows the ‘[...]’
+indicator. You can set user option ‘kubed-list-mode-line-format’ to
+customize this indicator.
+
+* Menu:
+
+* List Filter::
+
+\1f
+File: doc5HCYE0.info, Node: List Filter, Up: Browse Resources
+
+3.2.1 Filtering Resource Lists
+------------------------------
+
+In all Kubernetes resource list buffers, you can use ‘/’
+(‘kubed-list-set-filter’) to “filter” the list of resources. This
+command sets the filter of the current buffer, which specifies
+conditions on resources (lines). Resources that don’t satisfy the
+conditions are hidden, so you can focus on those that do.
+
+ Filters can be simple (“atomic filters”) or complex (“composed
+filters”). In the simple case, you enter a filter in the minibuffer in
+the format ‘OP COL VAL’, where OP is a comparison operator, one of ‘=’,
+‘~’, ‘<’ and ‘>’; COL is a column name; and VAL is a value to compare to
+values of COL with OP.
+
+ If OP is ‘=’, it says to keep only lines whose COL equals VAL. If OP
+is ‘~’, it says to keep lines whose COL matches the VAL as a regular
+expression. ‘<’ and ‘>’ compare column values to numeric values. For
+example, the filter ‘= Name foobar’ keeps only resources whose name is
+‘foobar’. To include whitespace in VAL, wrap VAL in double quotes: ‘=
+Name "foo bar"’.
+
+ The possible values of OP and their meanings are defined in
+‘kubed-list-filter-operator-alist’, which is a user option you can use
+to add or change the meaning of filter operators:
+
+ -- User Option: kubed-list-filter-operator-alist
+ Association list of filter operators and corresponding Emacs Lisp
+ functions. See the documentation string of this option for
+ specifics.
+
+ You can use more refined filters by composing simple filters. To add
+another filter that resources must satisfy, wrap your two filters in
+parentheses and put them next to each other:
+
+ (= Name foobar) (~ Namespace kube)
+
+This filter keeps only resources whose name is ‘foobar’ in namespaces
+that include ‘kube’ as a substring, it expresses the conjunction of the
+two simple filters.
+
+ To negate a simple filter, put a single quote ahead of it, like so:
+
+ (= Name foobar) '(~ Namespace kube)
+
+This matches resources named ‘foobar’ in namespaces that do _not_
+contain ‘kube’.
+
+ To specify a disjunction of simple filters (and negated simple
+filters), add another level of nesting:
+
+ ((= Name spam) (= Name foobar)) '(~ Namespace kube)
+
+This filter matches resources named either ‘spam’ or ‘foobar’, in
+namespaces that do not contain ‘kube’.
+
+ While entering a filter in the minibuffer, ‘<TAB>’ completion is
+available for column names and values. To clear the filter, just exit
+the minibuffer with an empty input.
+
+\1f
+File: doc5HCYE0.info, Node: Context and Namespace, Next: Transient Menus, Prev: Browse Resources, Up: Usage
+
+3.3 Context and Namespace
+=========================
+
+Kubed uses ‘kubectl’ to interact with Kubernetes clusters, and you can
+use multiple ‘kubectl’ “contexts” in Kubed to work with different
+Kubernetes clusters in parallel. Within each context, Kubed lets you
+work in multiple “namespaces” too. In general, each buffer has its own
+“local context” and “local namespace”, and Kubed commands use these
+local context and namespace by default. This is similar to how
+different buffers have different values of ‘default-directory’ (*note
+(emacs)File Names::), and how that affects commands that interact with
+the filesystem.
+
+ For Kubed buffers that display a Kubernetes resource (*note Display
+Resource::) or a list of resources (*note Browse Resources::), the
+buffer-local context and namespace are the context and namespace of the
+resources the buffer displays. For non-Kubed buffers, the user option
+‘kubed-default-context-and-namespace’ determines the default local
+context and namespace.
+
+ -- User Option: kubed-default-context-and-namespace
+ Default ‘kubectl’ context and Kubernetes namespace for Kubed
+ commands. The value of this option is either a cons cell
+ ‘(CONTEXT . NAMESPACE)’ or ‘nil’. If it is ‘nil’, Kubed
+ initializes this option to a cons cell with the current ‘kubectl’
+ context and namespace for that context on the first time Kubed
+ consults this option.
+
+ As mentioned above, most Kubed commands use the local context and
+namespace by default—it is also conventional for Kubed commands to let
+you choose a different namespace if you invoke them with a prefix
+argument (‘C-u’), and to also let you choose a different context if you
+invoke them with a double prefix argument (‘C-u C-u’).
+
+ The following functions return the local context and namespace for
+buffer in which you call them:
+
+ -- Function: kubed-local-context
+ Return the local ‘kubectl’ context for the current buffer.
+
+ -- Function: kubed-local-namespace
+ Return the local Kubernetes namespace for the current buffer.
+
+ -- Function: kubed-local-context-and-namespace
+ Return the local context and namespace for the current buffer as a
+ cons cell ‘(CONTEXT . NAMESPACE)’.
+
+\1f
+File: doc5HCYE0.info, Node: Transient Menus, Next: Scale Deployment, Prev: Context and Namespace, Up: Usage
+
+3.4 Transient Menus
+===================
+
+Kubed provides a set of keyboard-driven “transient menus” (*note
+(transient)Top::) that you can use to explore and invoke various
+commands. Transient menus show you available commands along with their
+key bindings, which helps you use many different commands without the
+need to remember the key bindings for all them. Additionally, the
+transient menus allow you to specify options and switches for the
+commands you invoke, which may not be available when invoking these
+commands directly. For example, while you can invoke command
+‘kubed-logs’ directly to view logs from a container running in
+Kubernetes, the “Kubernetes Logs” transient menu which you open with
+‘kubed-transient-logs’ lets you set a maximum number of logs to
+retrieve, among other knobs and handles.
+
+ Command ‘kubed-transient’ opens the Kubed “root” transient menu,
+which is a dispatch that lets you access different transient menus, such
+as the aforementioned “Kubernetes Logs” transient menu. It’s a good
+idea to bind ‘kubed-transient’ to a convenient key, either in the global
+keymap or under ‘kubed-prefix-map’:
+
+ (keymap-global-set "s-k" #'kubed-transient)
+ (keymap-set kubed-prefix-map "k" #'kubed-transient)
+
+ In Kubed resource list buffers, type ‘?’ to pop up a transient menu
+with commands that are specific to the type of resources the buffer
+displays. *Note Browse Resources::.
+
+\1f
+File: doc5HCYE0.info, Node: Scale Deployment, Prev: Transient Menus, Up: Usage
+
+3.5 Scale Deployment
+====================
+
+You can use Kubed to “scale” Kubernetes a deployment, which means
+setting the size of the deployment in terms of the number of replicas it
+governs.
+
+ -- Command: kubed-scale-deployment
+ Scale a Kubernetes deployment.
+
+ ‘kubed-scale-deployment’ is bound to ‘$’ in the
+‘kubed-deployment-prefix-map’, so if you have ‘kubed-prefix-map’ bound
+to ‘C-c k’ then you can invoke it with ‘C-c k d $’. *Note Definition of
+kubed-prefix-map::. This command prompts you for a deployment and then
+lets you select a new number of replicas for that deployment. By
+default you are prompted for deployment in the local context and
+namespace (*note Context and Namespace::); with a prefix argument, you
+can specify a different namespace; with a double prefix argument (‘C-u
+C-u’), you can specify a different context too.
+
+ In the deployments list buffer, you can scale the deployment at point
+by simply typing ‘$’. *Note Browse Resources::.
+
+ Lastly, you can scale deployments using the transient menu
+‘kubed-transient-scale-deployment’, which is available from the main
+Kubed transient by pressing ‘$’. *Note Transient Menus::.
+
+\1f
+File: doc5HCYE0.info, Node: Extending Kubed, Next: Contributing, Prev: Usage, Up: Top
+
+4 Extending Kubed
+*****************
+
+Kubed has built-in support for many common types of Kubernetes
+resources. “Support” for a resource type in Kubed just means providing
+common commands for working with resources of that type, such as a
+command for showing the interactive list of these resources, like
+‘kubed-list-pods’ for Kubernetes pods. In Kubed, the macro
+‘kubed-define-resource’ is used to define these common commands and add
+support for more resource types:
+
+ -- Macro: kubed-define-resource resource &optional properties &rest [
+ keyword argument ]... commands
+ Define commands for RESOURCE.
+
+ Say you want to add support for your Kubernetes service accounts.
+With ‘kubed-define-resource’, all you need to do is to add the following
+line to your Emacs initialization file:
+
+ (kubed-define-resource serviceaccount)
+
+ This line defines the following commands for you:
+
+ • ‘kubed-display-serviceaccount’
+ • ‘kubed-list-serviceaccounts’
+ • ‘kubed-edit-serviceaccount’
+ • ‘kubed-delete-serviceaccount’
+ • ‘kubed-create-serviceaccount’
+ • ‘kubed-explain-serviceaccounts’
+
+ See the documentation string of ‘kubed-define-resource’ (‘C-h f’) for
+details about the optional arguments of this macro: the PROPERTIES
+argument which lets you specify what Kubed should show in the resource
+list buffer and the ‘commands’ argument which lets you associate
+additional commands with your resource type.
+
+\1f
+File: doc5HCYE0.info, Node: Contributing, Next: Indices, Prev: Extending Kubed, Up: Top
+
+5 Contributing
+**************
+
+We highly appreciate all contributions, including bug reports, patches,
+improvement suggestions, and general feedback!
+
+ The best way to get in touch with the Kubed maintainers is via the
+Kubed mailing list (https://lists.sr.ht/~eshel/kubed-devel).
+
+ -- Command: kubed-submit-bug-report
+ Report a bug in Kubed to the maintainers via mail.
+
+ You can use the command ‘M-x kubed-submit-bug-report’ to easily
+contact the Kubed maintainers from within Emacs. This command opens a
+new buffer with a message template ready to be sent to the development
+mailing list.
+
+\1f
+File: doc5HCYE0.info, Node: Indices, Prev: Contributing, Up: Top
+
+Indices
+*******
+
+* Menu:
+
+* Function Index::
+* Variable Index::
+* Keystroke Index::
+* Concept Index::
+
+\1f
+File: doc5HCYE0.info, Node: Function Index, Next: Variable Index, Up: Indices
+
+Function index
+==============
+
+\0\b[index\0\b]
+* Menu:
+
+* kubed-define-resource: Extending Kubed. (line 14)
+* kubed-display-deployment: Display Resource. (line 14)
+* kubed-display-FOO: Display Resource. (line 16)
+* kubed-display-namespace: Display Resource. (line 15)
+* kubed-display-pod: Display Resource. (line 13)
+* kubed-display-resource: Display Resource. (line 10)
+* kubed-display-resource-diff: Display Resource. (line 78)
+* kubed-display-resource-jump-to-list: Display Resource. (line 74)
+* kubed-display-resource-mode: Display Resource. (line 63)
+* kubed-display-resource-replace: Display Resource. (line 78)
+* kubed-list-copy-as-kill: Browse Resources. (line 71)
+* kubed-list-delete: Browse Resources. (line 51)
+* kubed-list-delete-marked: Browse Resources. (line 67)
+* kubed-list-display-resource: Browse Resources. (line 48)
+* kubed-list-edit: Browse Resources. (line 53)
+* kubed-list-fit-column-width-to-content: Browse Resources. (line 88)
+* kubed-list-mark-for-deletion: Browse Resources. (line 63)
+* kubed-list-mode: Browse Resources. (line 35)
+* kubed-list-next-column: Browse Resources. (line 78)
+* kubed-list-patch: Browse Resources. (line 55)
+* kubed-list-previous-column: Browse Resources. (line 82)
+* kubed-list-select-resource: Browse Resources. (line 42)
+* kubed-list-select-resource-other-window: Browse Resources. (line 45)
+* kubed-list-set-filter: Browse Resources. (line 60)
+* kubed-list-transient: Browse Resources. (line 41)
+* kubed-list-unmark: Browse Resources. (line 69)
+* kubed-list-update: Browse Resources. (line 57)
+* kubed-local-context: Context and Namespace.
+ (line 40)
+* kubed-local-context-and-namespace: Context and Namespace.
+ (line 46)
+* kubed-local-namespace: Context and Namespace.
+ (line 43)
+* kubed-menu-bar-mode: Usage. (line 34)
+* kubed-scale-deployment: Scale Deployment. (line 10)
+* kubed-submit-bug-report: Contributing. (line 12)
+* kubed-transient: Usage. (line 40)
+* next-line: Browse Resources. (line 74)
+* previous-line: Browse Resources. (line 76)
+* read-only-mode: Display Resource. (line 78)
+* revert-buffer: Display Resource. (line 87)
+* revert-buffer-quick: Display Resource. (line 87)
+* tabulated-list-narrow-current-column: Browse Resources. (line 93)
+* tabulated-list-sort: Browse Resources. (line 86)
+* tabulated-list-widen-current-column: Browse Resources. (line 96)
+
+\1f
+File: doc5HCYE0.info, Node: Variable Index, Next: Keystroke Index, Prev: Function Index, Up: Indices
+
+Variable index
+==============
+
+\0\b[index\0\b]
+* Menu:
+
+* kubed-default-context-and-namespace: Context and Namespace.
+ (line 23)
+* kubed-kubectl-program: Getting Started. (line 24)
+* kubed-list-filter-operator-alist: List Filter. (line 29)
+* kubed-list-mode-line-format: Browse Resources. (line 110)
+* kubed-prefix-map: Usage. (line 16)
+* kubed-yaml-setup-hook: Display Resource. (line 53)
+
+\1f
+File: doc5HCYE0.info, Node: Keystroke Index, Next: Concept Index, Prev: Variable Index, Up: Indices
+
+Keystroke index
+===============
+
+\0\b[index\0\b]
+* Menu:
+
+* /: Browse Resources. (line 60)
+* <?>: Browse Resources. (line 41)
+* {: Browse Resources. (line 93)
+* |: Browse Resources. (line 88)
+* }: Browse Resources. (line 96)
+* C-c C-=: Display Resource. (line 78)
+* C-c C-c: Display Resource. (line 78)
+* C-c C-j: Display Resource. (line 74)
+* C-o: Browse Resources. (line 48)
+* C-x C-q: Display Resource. (line 78)
+* C-x x g: Display Resource. (line 87)
+* D: Browse Resources. (line 51)
+* d: Browse Resources. (line 63)
+* e: Browse Resources. (line 53)
+* g: Browse Resources. (line 57)
+* n: Browse Resources. (line 74)
+* o: Browse Resources. (line 45)
+* P: Browse Resources. (line 55)
+* p: Browse Resources. (line 76)
+* <RET>: Browse Resources. (line 42)
+* S: Browse Resources. (line 86)
+* S-<TAB>: Browse Resources. (line 82)
+* <TAB>: Browse Resources. (line 78)
+* u: Browse Resources. (line 69)
+* w: Browse Resources. (line 71)
+* x: Browse Resources. (line 67)
+
+\1f
+File: doc5HCYE0.info, Node: Concept Index, Prev: Keystroke Index, Up: Indices
+
+Concept index
+=============
+
+\0\b[index\0\b]
+* Menu:
+
+* global prefix keymap: Usage. (line 16)
+* installation: Getting Started. (line 6)
+* keymap, for global commands: Usage. (line 16)
+* Kubed: Overview. (line 6)
+* kubed: Overview. (line 6)
+* prefix keymap, global commands: Usage. (line 16)
+* requirements: Getting Started. (line 14)
+
+
+\1f
+Tag Table:
+Node: Top\7f596
+Node: Overview\7f1140
+Node: Getting Started\7f1908
+Node: Usage\7f3005
+Ref: Definition of kubed-prefix-map\7f3616
+Ref: Definition of kubed-menu-bar-mode\7f4379
+Node: Display Resource\7f5014
+Node: Browse Resources\7f9544
+Node: List Filter\7f14571
+Node: Context and Namespace\7f17224
+Node: Transient Menus\7f19614
+Node: Scale Deployment\7f21189
+Node: Extending Kubed\7f22486
+Node: Contributing\7f24077
+Node: Indices\7f24777
+Node: Function Index\7f24951
+Node: Variable Index\7f28346
+Node: Keystroke Index\7f29009
+Node: Concept Index\7f31069
+\1f
+End Tag Table
+
+\1f
+Local Variables:
+coding: utf-8
+End:
--- /dev/null
+\input texinfo @c -*- texinfo -*-
+@c %**start of header
+@setfilename kubed.info
+@settitle Kubed: Kubernetes, Emacs, done!
+@documentencoding UTF-8
+@documentlanguage en
+@set MAINTAINERSITE @uref{https://eshelyaron.com,maintainer webpage}
+@set MAINTAINER Eshel Yaron
+@set MAINTAINEREMAIL @email{meNOSPAMeshelyaron.com}
+@set MAINTAINERCONTACT @uref{mailto:meNOSPAMeshelyaron.com,contact the maintainer}
+@c %**end of header
+
+@copying
+This manual is for Kubed 0.4.2, a rich Emacs interface for Kubernetes.
+
+Copyright @copyright{} 2024 Eshel Yaron.
+
+@quotation
+Permission is granted to copy, distribute and/or modify this document
+under the terms of the GNU Free Documentation License, Version 1.3 or
+any later version published by the Free Software Foundation; with no
+Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
+@end quotation
+@end copying
+
+@dircategory Emacs
+@direntry
+* Kubed: (kubed). Kubernetes, Emacs, done!
+@end direntry
+
+@finalout
+@titlepage
+@title Kubed: Kubernetes, Emacs, done!
+@author Eshel Yaron (@email{me@@eshelyaron.com})
+@end titlepage
+
+@contents
+
+@ifnottex
+@node Top
+@top Kubed: Kubernetes, Emacs, done!
+
+This manual is for Kubed 0.4.2, a rich Emacs interface for Kubernetes.
+
+@end ifnottex
+
+@menu
+* Overview:: Introduction to Kubed
+* Getting Started:: First steps with Kubed
+* Usage:: Using Kubed to interact with Kubernetes
+* Extending Kubed:: Adding support for your custom resources
+* Contributing:: Get involved in Kubed development
+* Indices::
+@end menu
+
+@node Overview
+@chapter Overview
+
+@cindex Kubed
+@cindex kubed
+Kubed is a rich Kubernetes interface within Emacs. It helps you work
+with your Kubernetes clusters and deployments with the full power of
+@command{kubectl}, and with the comfort and confidence of an intuitive
+interactive interface.
+
+@ifhtml
+@float
+@image{/assets/kubed-overview,,,Image: Four different kinds of Kubed buffers in Emacs,.png}
+@caption{Pods, logs, shells, YAMLs, all in one integrated interface}
+@end float
+@end ifhtml
+
+You can use Kubed to:
+
+@itemize
+@item
+Browse and manage Kubernetes workloads
+@item
+Connect to pods and edit files or execute commands
+@item
+Create new resources, edit and delete them
+@item
+Work with multiple clusters
+@item
+Get help about various Kubernetes objects
+@item
+@dots{}
+@end itemize
+
+These features and others are documented in the rest of this manual,
+along with many options that Kubed provides for you to customize its
+behavior.
+
+@node Getting Started
+@chapter Getting Started
+
+@cindex installation
+
+Get Kubed from GNU ELPA with @kbd{M-x package-install}, or use your
+favorite Emacs package manager to install Kubed from Git. You can
+clone the Kubed Git repository from any of the following locations:
+
+@itemize
+@item
+@url{https://git.sr.ht/~eshel/kubed}
+@item
+@url{https://github.com/eshelyaron/kubed.git}
+@item
+@url{git://git.eshelyaron.com/kubed.git}
+@end itemize
+
+@cindex requirements
+To get started with Kubed, all you need is @command{kubectl} and Emacs.
+
+Kubed tries to find @command{kubectl} in the directories listed in the
+Emacs variable @code{exec-path}. When Emacs is started from a shell, it
+initializes @code{exec-path} from the shell's @env{PATH} environment
+variable which normally includes the location of @command{kubectl} in
+common @command{kubectl} installations. If Emacs doesn't find the
+@command{kubectl} executable via @code{exec-path}, you can tell Kubed
+where to find it by customizing @code{kubed-kubectl-program}.
+
+@defopt kubed-kubectl-program
+Name of @command{kubectl} executable file to use.
+@end defopt
+
+@node Usage
+@chapter Usage
+
+Kubed comes with a large number of Emacs command for interacting with
+Kubernetes. Some commands perform one action on one resource, while
+others show you a menu with many resources and let you interact with any
+number of them. Some commands are specific to one type of resources,
+while others act on resources of any type. There are different ways to
+use Kubed, depending on your use case.
+
+The two most important entry points for Kubed are
+@code{kubed-prefix-map} and @code{kubed-transient}:
+
+@anchor{Definition of kubed-prefix-map}
+@cindex global prefix keymap
+@cindex prefix keymap, global commands
+@cindex keymap, for global commands
+@defvr Keymap kubed-prefix-map
+Keymap with bindings for many useful Kubed commands.
+@end defvr
+
+The prefix keymap @code{kubed-prefix-map} gives you quick access to the
+main Kubed commands. You may want to bind it to a convenient key in
+your global keymap, for example:
+
+@lisp
+(keymap-global-set "C-c k" 'kubed-prefix-map)
+@end lisp
+
+With this binding in place, you can create a new Kubernetes resource
+with @kbd{C-c k +} and attach to a running pod with @kbd{C-c k p a}.
+The @kbd{C-c k} binding is just a suggestion of course, feel free to
+pick any key sequence you like.
+
+@ifhtml
+@float
+
+@image{/assets/kubed-prefix-map,,,Image: Emacs which-key popup for kubed-prefix-map keymap,.png}
+
+@image{/assets/kubed-deployment-prefix-map,,,Image: Emacs which-key popup for kubed-deployment-prefix-map,.png}
+
+@caption{Use @code{kubed-prefix-map} for quick access to the main Kubed commands}
+@end float
+@end ifhtml
+
+You can also access all of the commands in @code{kubed-prefix-map} via
+the menu-bar @samp{Kubernetes} menu---to add this menu to your
+menu-bar, enable the global minor mode @code{kubed-menu-bar-mode}:
+
+@anchor{Definition of kubed-menu-bar-mode}
+@deffn {Minor Mode} kubed-menu-bar-mode
+Add @samp{Kubernetes} menu to your menu bar.
+@end deffn
+
+The other important entry point that Kubed provides for exploring and
+performing different Kubernetes operations is @code{kubed-transient}:
+
+@deffn Command kubed-transient
+Open the main Kubed transient menu.
+@end deffn
+
+This command pops up the Kubed transient menu, which gives you
+interactive guidance for invoking Kubed commands.
+@xref{Top,,,transient}.
+
+The following sections describe in detail the various Kubed commands.
+
+@menu
+* Display Resource::
+* Browse Resources::
+* Context and Namespace::
+* Transient Menus::
+* Scale Deployment::
+@end menu
+
+@node Display Resource
+@section Display Resource
+
+You can view the YAML description of any resource in your Kubernetes
+cluster. In Kubed, this is referred to as @dfn{displaying} the given
+resource. The following commands display a Kubernetes resource:
+
+@deffn Command kubed-display-resource
+Display a Kubernetes resource.
+@end deffn
+
+@deffn Command kubed-display-pod
+@deffnx Command kubed-display-deployment
+@deffnx Command kubed-display-namespace
+@deffnx Command kubed-display-@var{foo}
+Display Kubernetes resource of a given type.
+@end deffn
+
+Command @code{kubed-display-resource} can display any Kubernetes
+resource. It prompts you to select a resource type and a resource
+name, and displays that resource in a dedicated buffer. By default,
+this command assumes you want to display a resource that is in the
+current namespace, but if you invoke @code{kubed-display-resource}
+with a prefix argument (@kbd{C-u}) and choose a namespaced resource
+type, then it also lets you select the namespace of the resource.
+With a double prefix argument, it lets you select which
+@command{kubectl} context to use too. @xref{Context and Namespace}.
+
+If you have @code{kubed-prefix-map} bound to @kbd{C-c k}, you can
+invoke @code{kubed-display-resource} with @kbd{C-c k @key{RET}}. This
+command is also available from the menu-bar ``Kubernetes'' menu
+(@pxref{Definition of kubed-menu-bar-mode}), and from the transient
+``Kubernetes Display'' menu (@pxref{Transient Menus}).
+
+You can also display the resource at point in resources list buffers
+by simply typing @kbd{@key{RET}}. @xref{Browse Resources}.
+
+Kubed also provides a dedicated @code{kubed-display-@var{foo}} command
+for each supported resource type @var{foo}. These commands are just
+like @code{kubed-display-resource}, except that they do not prompt you
+for a type, since they are type-specific. For example,
+@code{kubed-display-pod} only prompts you for a pod name, and then
+displays it like @code{kubed-display-resource} does.
+
+You can invoke these commands by adding a type-specific key before
+@kbd{@key{RET}} in @kbd{C-c k @key{RET}}: @kbd{C-c k p @key{RET}}
+invokes @code{kubed-display-pod}, @kbd{C-c k d @key{RET}} invokes
+@code{kubed-display-deployment}, and so on.
+
+When Kubed prepares a buffer for displaying a resource, it runs the
+hook @code{kubed-yaml-setup-hook} right after populating the buffer
+with the YAML description of the resource:
+
+@defopt kubed-yaml-setup-hook
+List of functions to call, in order, to prepare YAML buffers. The
+first function on this hook is normally the major mode you want to use
+for YAML buffers that display Kubernetes resources. By default, this
+is the list @code{(yaml-ts-mode view-mode)}.
+@end defopt
+
+In addition, Kubed enables a bespoke minor mode
+@code{kubed-display-resource-mode} in buffers that display Kubernetes
+resources:
+
+@deffn {Minor Mode} kubed-display-resource-mode
+Minor mode for buffers that display a Kubernetes resource.
+@end deffn
+
+@noindent
+This minor mode does the following:
+
+@itemize
+@item
+It shows the word @samp{Kubed} in the mode line, so you know you're
+seeing a rendered Kubernetes resource, rather than any old YAML file.
+@item
+It allows you to @emph{bookmark} the resource your viewing and your
+position in its YAML description with the regular Emacs bookmarks
+commands (@pxref{Bookmarks,,,emacs}), namely @kbd{C-x r m}.
+@item
+@kindex C-c C-j
+@findex kubed-display-resource-jump-to-list
+It binds the key sequence @kbd{C-c C-j} to command
+@code{kubed-display-resource-jump-to-list}, which pops up the
+resources list buffer for the type of the displayed resource, and
+takes you to the line corresponding to it.
+@item
+@kindex C-c C-c
+@findex kubed-display-resource-replace
+@kindex C-c C-=
+@findex kubed-display-resource-diff
+@kindex C-x C-q
+@findex read-only-mode
+It binds @kbd{C-c C-c} to command
+@code{kubed-display-resource-replace}, which lets you apply any
+changes that you make in the YAML buffer back to the resource. When
+you hit @kbd{C-c C-c}, Kubed asks you for confirmation before
+proceeding; you can reply with @kbd{d} (@samp{diff}) to show a diff
+between the current state of the resource and the current buffer
+contents. You can also display this diff anytime with @kbd{C-c C-=}.
+Note that by default Kubed displays resources in read-only buffers, so
+you may need to hit @kbd{C-x C-q} (@code{read-only-mode}) before
+making changes in the buffer.
+@item
+@kindex C-x x g
+@findex revert-buffer
+@findex revert-buffer-quick
+It lets you repopulate the buffer with updated information about the
+resource with @kbd{C-x x g} (@code{revert-buffer-quick}) and
+@w{@kbd{M-x revert-buffer}}. This is also useful if you edit the
+resource and want to restart from a clean slate.
+@end itemize
+
+@ifhtml
+@float
+@image{/assets/kubed-replace-diff,,,Image: Displaying and editing YAML of Kubernetes pod with Kubed,.png}
+@caption{Type @kbd{C-x C-q} in a resource YAML buffer to edit it, @kbd{C-c C-c} to apply}
+@end float
+@end ifhtml
+
+@node Browse Resources
+@section Browse Resources
+
+Kubed gives you an interactive list of resources for each type, that
+you access with the @code{kubed-list-@var{resource}s} commands, such
+as @code{kubed-list-pods}.
+
+These commands are bound to @kbd{l} in Kubed's type-specific prefix
+keymaps, most of which are accessible via the global
+@code{kubed-prefix-map} by default (@pxref{Definition of
+kubed-prefix-map}). So if you've bound @code{kubed-prefix-map} to
+@kbd{C-c k}, you can pop up the pods list with @kbd{C-c k p l}, the
+deployments list with @kbd{C-c k d l}, the jobs list with @w{@kbd{C-c
+k j l}}, etc. Normally, Kubed shows the list of resources in the
+current namespace, but if you invoke these commands with a prefix
+argument (say, @kbd{C-u C-c k p l}), they first prompt you to select a
+namespace, and then show resources in the namespace you selected.
+
+The list of @var{resource}s in shown a buffer called @w{@file{*Kubed
+@var{resource}s@@@var{namespace}[@var{context}]*}}, where
+@var{context} is a @command{kubectl} context, and @var{namespace} is a
+namespace in @var{context}. For example, Kubed shows the list of pods
+in namespace @samp{default} for context @samp{minikube} in buffer
+@file{*Kubed pos@@default[minikube]}. For non-namespaced resources,
+the @w{@samp{@@@var{namespace}}} part is omitted.
+
+Each resource type has its own major mode for list buffers with
+type-specific commands and operations, but they are all also derived
+from a shared parent mode. The pods list uses the
+@code{kubed-pods-mode} (@samp{Kubernetes Pods}) major mode, the list
+of Kubernetes deployments uses @code{kubed-deployments-mode}, and so
+on.
+
+@ifhtml
+@float
+@image{/assets/kubed-list-transient,,,Image: Kubed pods list buffer with the help transient menu visible,.png}
+@caption{Browse your pods, deployments, and other resources with Kubed list buffers}
+@end float
+@end ifhtml
+
+The common parent major mode is @code{kubed-list-mode}:
+
+@deffn {Major Mode} kubed-list-mode
+Major mode for listing generic Kubernetes resources.
+@end deffn
+
+This mode provides the following commands for all type-specific
+derived modes:
+
+@table @kbd
+@kindex @key{?}
+@findex kubed-list-transient
+@item @key{?}
+Show help in transient menu (@code{kubed-list-transient}).
+@kindex @key{RET}
+@findex kubed-list-select-resource
+@item @key{RET}
+Display the resource at point in the current window
+(@code{kubed-list-select-resource}).
+@kindex o
+@findex kubed-list-select-resource-other-window
+@item o
+Display the resource at point in another window and select that window
+(@code{kubed-list-select-resource-other-window}).
+@kindex C-o
+@findex kubed-list-display-resource
+@item C-o
+Display the resource at point in another window without selecting it
+(@code{kubed-list-display-resource}).
+@kindex D
+@findex kubed-list-delete
+@item D
+Delete the resource at point (@code{kubed-list-delete}).
+@kindex e
+@findex kubed-list-edit
+@item e
+Edit the resource at point (@code{kubed-list-edit}).
+@kindex P
+@findex kubed-list-patch
+@item P
+Patch the resource at point (@code{kubed-list-patch}).
+@kindex g
+@findex kubed-list-update
+@item g
+Update the resources list by fetching new information from Kubernetes
+(@code{kubed-list-update}).
+@kindex /
+@findex kubed-list-set-filter
+@item /
+Filter the current list of resources (@code{kubed-list-set-filter}).
+@xref{List Filter}.
+@kindex d
+@findex kubed-list-mark-for-deletion
+@item d
+Mark the resource at point for deletion
+(@code{kubed-list-mark-for-deletion}). Resources marked for deletion
+have a @samp{D} in the beginning of their line.
+@kindex x
+@findex kubed-list-delete-marked
+@item x
+Delete marked resources (@code{kubed-list-delete-marked}).
+@kindex u
+@findex kubed-list-unmark
+@item u
+Unmark from resource at point (@code{kubed-list-unmark}).
+@kindex w
+@findex kubed-list-copy-as-kill
+@item w
+Copy name of resource at point into the kill ring
+(@code{kubed-list-copy-as-kill}).
+@kindex n
+@findex next-line
+@item n
+Move point to the next line (@code{next-line}).
+@kindex p
+@findex previous-line
+@item p
+Move point to the previous line (@code{previous-line}).
+@kindex @key{TAB}
+@findex kubed-list-next-column
+@item @key{TAB}
+Move point to the next column. If point is already at the last
+column, move to the first column of the next line instead
+(@code{kubed-list-next-column}).
+@kindex S-@key{TAB}
+@findex kubed-list-previous-column
+@item S-@key{TAB}
+Move point to the previous column. If point is already at the first
+column, move to the last column of the previous line instead
+(@code{kubed-list-previous-column}).
+@kindex S
+@findex tabulated-list-sort
+@item S
+Sort lines by the column at point (@code{tabulated-list-sort}).
+@kindex |
+@findex kubed-list-fit-column-width-to-content
+@item |
+Fit width of column at point to its content
+(@code{kubed-list-fit-column-width-to-content}). With non-negative
+prefix argument @var{n} (for example, @kbd{0 |}), fit @var{n}th
+column. With negative prefix argument (@kbd{- |}), fit all columns.
+@kindex @{
+@findex tabulated-list-narrow-current-column
+@item @{
+Narrow the column at point
+(@code{tabulated-list-narrow-current-column}).
+@kindex @}
+@findex tabulated-list-widen-current-column
+@item @}
+Widen the column at point
+(@code{tabulated-list-widen-current-column}).
+@end table
+
+In addition, you can bookmark any Kubed resources list buffer with the
+usual @kbd{C-x r m}. @xref{Bookmarks,,,emacs}.
+
+Note that @code{kubed-list-mode} (and its type-specific derivatives)
+bind the @kbd{g} key, which is commonly bound to @code{revert-buffer},
+to @code{kubed-list-update}. The difference between these commands is
+that @code{revert-buffer} only refreshes the list display, without
+fetching updated data from Kubernetes. You can still invoke
+@code{revert-buffer} with @kbd{C-x x g} or @kbd{M-x}, if you need it.
+
+@vindex kubed-list-mode-line-format
+Whenever Kubed is fetching data from Kubernetes to update a resources
+list buffer, the mode line of that list buffer shows the @samp{[...]}
+indicator. You can set user option @code{kubed-list-mode-line-format}
+to customize this indicator.
+
+@menu
+* List Filter::
+@end menu
+
+@node List Filter
+@subsection Filtering Resource Lists
+
+In all Kubernetes resource list buffers, you can use @kbd{/}
+(@code{kubed-list-set-filter}) to @dfn{filter} the list of resources.
+This command sets the filter of the current buffer, which specifies
+conditions on resources (lines). Resources that don't satisfy the
+conditions are hidden, so you can focus on those that do.
+
+Filters can be simple (@dfn{atomic filters}) or complex (@dfn{composed
+filters}). In the simple case, you enter a filter in the minibuffer
+in the format @w{@samp{@var{op} @var{col} @var{val}}}, where @var{op}
+is a comparison operator, one of @samp{=}, @samp{~}, @samp{<} and
+@samp{>}; @var{col} is a column name; and @var{val} is a value to
+compare to values of @var{col} with @var{op}.
+
+If @var{op} is @samp{=}, it says to keep only lines whose @var{col}
+equals @var{val}. If @var{op} is @samp{~}, it says to keep lines
+whose @var{col} matches the @var{val} as a regular expression.
+@samp{<} and @samp{>} compare column values to numeric values. For
+example, the filter @samp{= Name foobar} keeps only resources whose
+name is @samp{foobar}. To include whitespace in @var{val}, wrap
+@var{val} in double quotes: @samp{= Name "foo bar"}.
+
+The possible values of @var{op} and their meanings are defined in
+@code{kubed-list-filter-operator-alist}, which is a user option you
+can use to add or change the meaning of filter operators:
+
+@defopt kubed-list-filter-operator-alist
+Association list of filter operators and corresponding Emacs Lisp
+functions. See the documentation string of this option for specifics.
+@end defopt
+
+You can use more refined filters by composing simple filters. To add
+another filter that resources must satisfy, wrap your two filters in
+parentheses and put them next to each other:
+
+@lisp
+(= Name foobar) (~ Namespace kube)
+@end lisp
+
+@noindent
+This filter keeps only resources whose name is @samp{foobar} in
+namespaces that include @samp{kube} as a substring, it expresses the
+conjunction of the two simple filters.
+
+To negate a simple filter, put a single quote ahead of it, like so:
+
+@lisp
+(= Name foobar) '(~ Namespace kube)
+@end lisp
+
+@noindent
+This matches resources named @samp{foobar} in namespaces that do
+@emph{not} contain @samp{kube}.
+
+To specify a disjunction of simple filters (and negated simple
+filters), add another level of nesting:
+
+@lisp
+((= Name spam) (= Name foobar)) '(~ Namespace kube)
+@end lisp
+
+@noindent
+This filter matches resources named either @samp{spam} or
+@samp{foobar}, in namespaces that do not contain @samp{kube}.
+
+While entering a filter in the minibuffer, @kbd{@key{TAB}} completion
+is available for column names and values. To clear the filter, just
+exit the minibuffer with an empty input.
+
+@node Context and Namespace
+@section Context and Namespace
+
+Kubed uses @command{kubectl} to interact with Kubernetes clusters, and
+you can use multiple @command{kubectl} @dfn{contexts} in Kubed to work
+with different Kubernetes clusters in parallel. Within each context,
+Kubed lets you work in multiple @dfn{namespaces} too. In general,
+each buffer has its own @dfn{local context} and @dfn{local namespace},
+and Kubed commands use these local context and namespace by default.
+This is similar to how different buffers have different values of
+@code{default-directory} (@pxref{File Names,,,emacs}), and how that
+affects commands that interact with the filesystem.
+
+For Kubed buffers that display a Kubernetes resource (@pxref{Display
+Resource}) or a list of resources (@pxref{Browse Resources}), the
+buffer-local context and namespace are the context and namespace of
+the resources the buffer displays. For non-Kubed buffers, the user
+option @code{kubed-default-context-and-namespace} determines the
+default local context and namespace.
+
+@defopt kubed-default-context-and-namespace
+Default @command{kubectl} context and Kubernetes namespace for Kubed
+commands. The value of this option is either a cons cell
+@w{@code{(@var{context} . @var{namespace})}} or @code{nil}. If it is
+@code{nil}, Kubed initializes this option to a cons cell with the
+current @command{kubectl} context and namespace for that context on
+the first time Kubed consults this option.
+@end defopt
+
+As mentioned above, most Kubed commands use the local context and
+namespace by default---it is also conventional for Kubed commands to
+let you choose a different namespace if you invoke them with a prefix
+argument (@kbd{C-u}), and to also let you choose a different context
+if you invoke them with a double prefix argument (@kbd{C-u C-u}).
+
+The following functions return the local context and namespace for
+buffer in which you call them:
+
+@defun kubed-local-context
+Return the local @command{kubectl} context for the current buffer.
+@end defun
+
+@defun kubed-local-namespace
+Return the local Kubernetes namespace for the current buffer.
+@end defun
+
+@defun kubed-local-context-and-namespace
+Return the local context and namespace for the current buffer as a
+cons cell @w{@code{(@var{context} . @var{namespace})}}.
+@end defun
+
+@node Transient Menus
+@section Transient Menus
+
+Kubed provides a set of keyboard-driven @dfn{transient menus}
+(@pxref{Top,,,transient}) that you can use to explore and invoke
+various commands. Transient menus show you available commands along
+with their key bindings, which helps you use many different commands
+without the need to remember the key bindings for all them.
+Additionally, the transient menus allow you to specify options and
+switches for the commands you invoke, which may not be available when
+invoking these commands directly. For example, while you can invoke
+command @code{kubed-logs} directly to view logs from a container
+running in Kubernetes, the ``Kubernetes Logs'' transient menu which
+you open with @code{kubed-transient-logs} lets you set a maximum
+number of logs to retrieve, among other knobs and handles.
+
+@ifhtml
+@float
+
+@image{/assets/kubed-transient,,,Image: The Kubed main transient menu,.png}
+
+@image{/assets/kubed-transient-exec,,,Image: The Kubed Exec transient menu,.png}
+
+@caption{Explore Kubed commands with transient menus}
+@end float
+@end ifhtml
+
+Command @code{kubed-transient} opens the Kubed ``root'' transient
+menu, which is a dispatch that lets you access different transient
+menus, such as the aforementioned ``Kubernetes Logs'' transient menu.
+It's a good idea to bind @code{kubed-transient} to a convenient key,
+either in the global keymap or under @code{kubed-prefix-map}:
+
+@lisp
+(keymap-global-set "s-k" #'kubed-transient)
+(keymap-set kubed-prefix-map "k" #'kubed-transient)
+@end lisp
+
+In Kubed resource list buffers, type @kbd{?} to pop up a transient
+menu with commands that are specific to the type of resources the
+buffer displays. @xref{Browse Resources}.
+
+@node Scale Deployment
+@section Scale Deployment
+
+You can use Kubed to @dfn{scale} Kubernetes a deployment, which means
+setting the size of the deployment in terms of the number of replicas
+it governs.
+
+@deffn Command kubed-scale-deployment
+Scale a Kubernetes deployment.
+@end deffn
+
+@code{kubed-scale-deployment} is bound to @kbd{$} in the
+@code{kubed-deployment-prefix-map}, so if you have
+@code{kubed-prefix-map} bound to @kbd{C-c k} then you can invoke it
+with @kbd{C-c k d $}. @xref{Definition of kubed-prefix-map}. This
+command prompts you for a deployment and then lets you select a new
+number of replicas for that deployment. By default you are prompted
+for deployment in the local context and namespace (@pxref{Context and
+Namespace}); with a prefix argument, you can specify a different
+namespace; with a double prefix argument (@kbd{C-u C-u}), you can
+specify a different context too.
+
+In the deployments list buffer, you can scale the deployment at point
+by simply typing @kbd{$}. @xref{Browse Resources}.
+
+@ifhtml
+@float
+@image{/assets/kubed-scale-deployments,,,Image: Help transient menu in Kubed deployments list buffer,.png}
+@caption{Scale deployments with @kbd{$} in the deployments list buffer}
+@end float
+@end ifhtml
+
+Lastly, you can scale deployments using the transient menu
+@code{kubed-transient-scale-deployment}, which is available from the
+main Kubed transient by pressing @kbd{$}. @xref{Transient Menus}.
+
+@node Extending Kubed
+@chapter Extending Kubed
+
+Kubed has built-in support for many common types of Kubernetes
+resources. ``Support'' for a resource type in Kubed just means
+providing common commands for working with resources of that type,
+such as a command for showing the interactive list of these resources,
+like @code{kubed-list-pods} for Kubernetes pods. In Kubed, the macro
+@code{kubed-define-resource} is used to define these common commands
+and add support for more resource types:
+
+@deffn Macro kubed-define-resource resource &optional properties &rest [ keyword argument ]@dots{} commands
+Define commands for @var{resource}.
+@end deffn
+
+Say you want to add support for your Kubernetes service accounts.
+With @code{kubed-define-resource}, all you need to do is to add the
+following line to your Emacs initialization file:
+
+@lisp
+(kubed-define-resource serviceaccount)
+@end lisp
+
+This line defines the following commands for you:
+
+@itemize
+@item
+@code{kubed-display-serviceaccount}
+@item
+@code{kubed-list-serviceaccounts}
+@item
+@code{kubed-edit-serviceaccount}
+@item
+@code{kubed-delete-serviceaccount}
+@item
+@code{kubed-create-serviceaccount}
+@item
+@code{kubed-explain-serviceaccounts}
+@end itemize
+
+See the documentation string of @code{kubed-define-resource}
+(@w{@kbd{C-h f}}) for details about the optional arguments of this
+macro: the @var{properties} argument which lets you specify what Kubed
+should show in the resource list buffer and the @code{commands} argument
+which lets you associate additional commands with your resource type.
+
+@node Contributing
+@chapter Contributing
+
+We highly appreciate all contributions, including bug reports,
+patches, improvement suggestions, and general feedback!
+
+The best way to get in touch with the Kubed maintainers is via
+@uref{https://lists.sr.ht/~eshel/kubed-devel, the Kubed mailing list}.
+
+@deffn Command kubed-submit-bug-report
+Report a bug in Kubed to the maintainers via mail.
+@end deffn
+
+You can use the command @kbd{M-x kubed-submit-bug-report} to easily
+contact the Kubed maintainers from within Emacs. This command opens a
+new buffer with a message template ready to be sent to the development
+mailing list.
+
+@node Indices
+@unnumbered Indices
+
+@menu
+* Function Index::
+* Variable Index::
+* Keystroke Index::
+* Concept Index::
+@end menu
+
+@node Function Index
+@unnumberedsec Function index
+
+@printindex fn
+
+@node Variable Index
+@unnumberedsec Variable index
+
+@printindex vr
+
+@node Keystroke Index
+@unnumberedsec Keystroke index
+
+@printindex ky
+
+@node Concept Index
+@unnumberedsec Concept index
+
+@printindex cp
+
+@bye
--- /dev/null
+;;; kubedoc-autoloads.el --- automatically extracted autoloads (do not edit) -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from kubedoc.el
+
+(autoload 'kubedoc "kubedoc" "\
+Show Kubernetes API documentation.
+Uses current context." t)
+(autoload 'kubedoc-for-context "kubedoc" "\
+Show Kubernetes API documentation.
+Prompts for changing current context." t)
+(register-definition-prefixes "kubedoc" '("kubedoc-"))
+\f
+;;; End of scraped data
+
+(provide 'kubedoc-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; kubedoc-autoloads.el ends here
--- /dev/null
+(define-package "kubedoc" "20240108.1404" "Kubernetes API Documentation"
+ '((emacs "27.1"))
+ :commit "aac02b096c98b83b4eaf129e6d767cf7150a6d43" :authors
+ '(("Dean Lindqvist Todevski" . "https://github.com/r0bobo"))
+ :keywords
+ '("docs" "help" "k8s" "kubernetes" "tools")
+ :url "https://github.com/r0bobo/kubedoc.el/")
+;; Local Variables:
+;; no-byte-compile: t
+;; End:
--- /dev/null
+;;; kubedoc.el --- Kubernetes API Documentation -*- lexical-binding: t; -*-
+;;
+;; Copyright (C) 2021, 2024 Dean Lindqvist Todevski
+;;
+;; Author: Dean Lindqvist Todevski <https://github.com/r0bobo>
+;; Maintainer: Dean Lindqvist Todevski
+;; Keywords: docs help k8s kubernetes tools
+;; Version: 1.0
+;; Homepage: https://github.com/r0bobo/kubedoc.el/
+;; Package-Requires: ((emacs "27.1"))
+
+;; This file is NOT part of GNU 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 3 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, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; kubedoc.el provides Kubernetes API documentation in Emacs.
+
+;;; Code:
+
+;;;; Requirements
+
+(require 'map)
+(require 'seq)
+(require 'subr-x)
+
+;;;; Completion style
+
+(add-to-list 'completion-category-overrides '(kubedoc (styles partial-completion)))
+
+;;;; Declarations
+
+(defgroup kubedoc nil
+ "Kubernetes API documentation."
+ :group 'tools)
+
+(defcustom kubedoc-excluded-resources '("\\.metrics\\.k8s\\.io")
+ "List of regexps for Kubernetes resources to ignore.
+Some Aggregated APIs have no docs for example."
+ :type 'sexp)
+
+(defconst kubedoc--explain-field-regex
+ "^\\([ \t]+\\)\\(\\w+\\)[ \t]+\\(<.*>+\\)[ \t]*\\(-required-\\)?$")
+
+(defconst kubedoc-imenu-generic-expression
+ `((nil ,kubedoc--explain-field-regex 2 kubedoc--imenu-goto-field)))
+
+(defconst kubedoc-font-lock-defaults
+ `(("^GROUP\\|^KIND\\|^VERSION\\|^RESOURCE\\|^DESCRIPTION\\|^FIELDS?" . font-lock-keyword-face)
+ (,kubedoc--explain-field-regex 3 font-lock-comment-face)
+ ("-required-" . font-lock-function-name-face)))
+
+(defvar kubedoc-mode-map
+ (let ((map (make-sparse-keymap)))
+ (suppress-keymap map)
+ (set-keymap-parent map (make-composed-keymap button-buffer-map special-mode-map))
+ map))
+
+(defvar kubedoc--field-completion-table-cache (make-hash-table :test 'equal))
+
+(defvar kubedoc--resource-completion-table-cache nil)
+
+(defvar kubedoc--current-context nil)
+
+(defvar-local kubedoc--buffer-path nil)
+
+(define-button-type 'kubedoc-field
+ 'follow-link t
+ 'help-echo "mouse-2, RET: display this section"
+ 'action #'kubedoc--field-button-function)
+
+
+;;;; Commands
+
+;;; UI
+
+(defun kubedoc--imenu-goto-field (_name position)
+ "Jump to correctly indented field POSITION."
+ (goto-char (+ position 3)))
+
+(defun kubedoc--highlight-field-links ()
+ "Create field link buttons in current buffer."
+ (while (re-search-forward kubedoc--explain-field-regex nil t)
+ (make-button
+ (match-beginning 2)
+ (match-end 2)
+ 'type 'kubedoc-field
+ 'kubectl-section (match-string 2))))
+
+(defun kubedoc--field-button-function (button)
+ "Follow field link in BUTTON."
+ (with-current-buffer (current-buffer)
+ (let ((field (button-get button 'kubectl-section)))
+ (apply #'kubedoc--view-resource (append kubedoc--buffer-path (list field))))))
+
+;;; Helpers
+
+(defun kubedoc--kubectl-command (&rest args)
+ "Run kubectl with ARGS and write output to current buffer."
+ (when kubedoc--current-context
+ (push (concat "--context=" kubedoc--current-context) args))
+ (let ((stderr (make-temp-file "emacs-kubedoc-kubectl-stderr-"))
+ (kubectl (executable-find "kubectl")))
+ (unwind-protect
+ (when-let* ((returncode (apply #'call-process kubectl nil `(t ,stderr) nil args))
+ ((/= returncode 0))
+ (errmsg (with-temp-buffer
+ (insert-file-contents stderr)
+ (buffer-string))))
+ (user-error (format "kubedoc.el kubectl error: %s" errmsg)))
+ (delete-file stderr))))
+
+;;; Parse functions
+
+(defun kubedoc--parse-api-resources ()
+ "Read output of `kubectl api-resources' from buffer and return as list.
+Excludes resouces that match regexps in `kubedoc-excluded-resources'."
+ (let ((excluded-resource-p (lambda (elt)
+ (seq-every-p
+ (lambda (re)
+ (not (string-match-p re elt))) kubedoc-excluded-resources))))
+ (thread-last
+ (split-string (buffer-string) nil t)
+ (seq-filter excluded-resource-p)
+ (seq-map (apply-partially #'format "%s/")))))
+
+(defun kubedoc--parse-kubectl-explain-fields ()
+ "Parse resource document from current buffer.
+Buffer should contain output from `kubectl explain --recursive'.
+Result is list of full paths for current resource.
+Supports both OpenAPI v2 and v3 schema."
+ (let ((result '())
+ (path '())
+ (base -1))
+ (goto-char (point-min))
+ (while (not (eobp))
+ (let ((line (thing-at-point 'line)))
+ (when (string-match kubedoc--explain-field-regex line)
+ (let* ((depth (current-indentation))
+ (field (match-string 2 line)))
+ ;; Set base indention length from first iteration
+ (when (= base -1)
+ (setq base depth))
+ (let ((position (/ (- depth base) base)))
+ ;; Drop all previous path elements that are
+ ;; deeper than the current.
+ (setq path (reverse (seq-subseq (reverse path) 0 position)))
+ (push field path)
+ (push (string-join (reverse path) "/") result)))))
+ (forward-line 1))
+ ;; Filter result so that every result
+ ;; ends with the left field of each hierarchy.
+ ;; If the result contains '("kind" "metadata" "metadata/labels")
+ ;; the result should be '("kind" "metadata/labels")
+ (let ((full-path-p (lambda (elt)
+ (thread-last
+ result
+ (seq-some (apply-partially #'string-prefix-p (concat elt "/")))
+ (not)))))
+ (thread-last
+ result
+ (seq-filter full-path-p)
+ (reverse)))))
+
+(defun kubedoc--kubectl-contexts ()
+ "List available kubectl contexts."
+ (with-temp-buffer
+ (kubedoc--kubectl-command "config" "get-contexts" "--output=name")
+ (seq-sort #'string< (split-string (buffer-string) nil t))))
+
+;;; Completion
+
+(defun kubedoc--resource-completion-table ()
+ "Completion candidate list for Kubernetes resources in the cluster."
+ (with-temp-buffer
+ (kubedoc--kubectl-command "api-resources" "--output" "name")
+ (kubedoc--parse-api-resources)))
+
+(defun kubedoc--resource-completion-table-cached ()
+ "Cached completion candidate list for Kubernetes resources in the cluster."
+ (if-let ((cached kubedoc--resource-completion-table-cache))
+ cached
+ (setq kubedoc--resource-completion-table-cache (kubedoc--resource-completion-table))))
+
+(defun kubedoc--field-completion-table (resource)
+ "Completion candidate list for all fields of Kubernetes RESOURCE."
+ (with-temp-buffer
+ (kubedoc--kubectl-command "explain" "--recursive" resource)
+ (thread-last
+ (kubedoc--parse-kubectl-explain-fields)
+ (seq-map (apply-partially #'format "%s/%s" resource)))))
+
+(defun kubedoc--field-completion-table-cached (resource)
+ "Cached Completion candidate list for all fields of Kubernetes RESOURCE."
+ (if-let (cached (map-elt kubedoc--field-completion-table-cache resource))
+ cached
+ (map-put! kubedoc--field-completion-table-cache resource (kubedoc--field-completion-table resource))))
+
+(defun kubedoc--completion-table (path)
+ "Completion candidate list for given Kubernetes resource and field PATH.
+PATH is a filesystem style path such as pods/spec/containers."
+ (let* ((string-parts (split-string path "/+"))
+ (resource (car string-parts)))
+ (if (= (length string-parts) 1)
+ (seq-filter
+ (lambda (e)
+ (string-prefix-p path e))
+ (kubedoc--resource-completion-table-cached))
+ (seq-uniq
+ (mapcar
+ (lambda (e)
+ (let* ((trimmed (string-remove-prefix (file-name-directory path) e))
+ (end (or (string-match-p "/" trimmed) (- (length trimmed) 1))))
+ (substring trimmed 0 (+ end 1))))
+ (seq-filter
+ (lambda (e)
+ (string-prefix-p path e))
+ (kubedoc--field-completion-table-cached resource)))
+ #'string-equal))))
+
+(defun kubedoc--completion-sort (collection)
+ "Completion candidate list sorting of COLLECTION.
+Sorts alphabetically with parent fields on top."
+ (seq-sort
+ (lambda (a b)
+ (let ((a-parent-p (string= (substring a -1) "/"))
+ (b-parent-p (string= (substring b -1) "/")))
+ (if (equal a-parent-p b-parent-p)
+ (string< a b)
+ a-parent-p)))
+ collection))
+
+(defun kubedoc--completion-function (string pred action)
+ "Completion for Kubernetes API resources.
+See argument STRING PRED ACTION descriptions in command `try-completion'."
+ (cond
+ ((eq action 'metadata)
+ '(metadata (category . kubedoc)
+ (display-sort-function . kubedoc--completion-sort)))
+ ((eq (car-safe action) 'boundaries)
+ (let* ((start (length (file-name-directory string)))
+ (end (string-match-p "/" (cdr action))))
+ `(boundaries ,start . ,end)))
+ (t
+ (let ((candidate
+ (if (string-equal string "")
+ string
+ (string-remove-prefix (or (file-name-directory string) "") string))))
+ (complete-with-action action (kubedoc--completion-table string) candidate pred)))))
+
+(defun kubedoc--view-resource (resource &rest field)
+ "Display Kubernetes api documentation for RESOURCE and optionally FIELD."
+ (let* ((path (append (list resource) field))
+ (prev-buffer (current-buffer))
+ (buffer (concat "*Kubernetes Docs <" (string-join path "/") ">*")))
+ (unless (get-buffer buffer)
+ (with-current-buffer (get-buffer-create buffer)
+ (kubedoc--kubectl-command "explain" (string-join path "."))
+ (when field
+ (goto-char (point-max))
+ (insert "\n")
+ (insert-text-button "Navigate up" 'action (lambda (_button) (kubedoc-up)))
+ (insert " ")
+ (insert-text-button "Navigate to top" 'action (lambda (_button) (kubedoc-top)))
+ (goto-char (point-min)))
+ (untabify (point-min) (point-max))
+ (set-buffer-modified-p nil)
+ (kubedoc-mode)
+ (setq-local kubedoc--buffer-path path)))
+ (if (with-current-buffer prev-buffer (equal major-mode 'kubedoc-mode))
+ (pop-to-buffer-same-window buffer)
+ (pop-to-buffer buffer))))
+
+(defun kubedoc--resource-path-canonical (resource &rest field)
+ "Return canonical path for Kubernetes RESOURCE and optionally FIELD.
+Paths are suffixed with a `/' if they contain any child fields."
+ (let* ((path (string-join (append (list resource) field) "/"))
+ (path-trailing-slash (concat path "/")))
+ (if (kubedoc--completion-table path-trailing-slash) path-trailing-slash path)))
+
+
+;;;; Interactive commands
+
+;;;###autoload
+(defun kubedoc ()
+ "Show Kubernetes API documentation.
+Uses current context."
+ (interactive)
+ (let* ((current-path (if-let ((current kubedoc--buffer-path))
+ (apply #'kubedoc--resource-path-canonical current)
+ ""))
+ (path (completing-read
+ "Kubernetes resource: " #'kubedoc--completion-function
+ nil nil current-path)))
+ (apply #'kubedoc--view-resource (split-string (string-trim-right path "/+") "/"))))
+
+;;;###autoload
+(defun kubedoc-for-context ()
+ "Show Kubernetes API documentation.
+Prompts for changing current context."
+ (interactive)
+ (kubedoc-set-context)
+ (kubedoc))
+
+(defun kubedoc-up ()
+ "Navigate to parent field of current Kubernetes resource."
+ (interactive)
+ (let ((parts (buffer-local-value 'kubedoc--buffer-path (current-buffer))))
+ (when (> (length parts) 1)
+ (apply #'kubedoc--view-resource (butlast kubedoc--buffer-path)))))
+
+(defun kubedoc-top ()
+ "Navigate to top of current Kubernetes resource."
+ (interactive)
+ (let ((parts (buffer-local-value 'kubedoc--buffer-path (current-buffer))))
+ (when (> (length parts) 1)
+ (apply #'kubedoc--view-resource (list (car kubedoc--buffer-path))))))
+
+(defun kubedoc-set-context ()
+ "Set current kubeconfig context."
+ (interactive)
+ (let* ((contexts (kubedoc--kubectl-contexts))
+ (context (completing-read "Kubernetes context: " contexts)))
+ ;; Invalidate cache if context is changed
+ (unless (string= context kubedoc--current-context)
+ (kubedoc-invalidate-cache))
+ (setq kubedoc--current-context context)))
+
+(defun kubedoc-invalidate-cache ()
+ "Invalidate kubedoc completion cache."
+ (interactive)
+ (setq kubedoc--field-completion-table-cache (make-hash-table :test 'equal))
+ (setq kubedoc--resource-completion-table-cache nil))
+
+
+;;;; Mode
+(define-derived-mode kubedoc-mode special-mode "kubedoc"
+ "Major mode for displaying Kubernetes api documentation."
+ (setq buffer-auto-save-file-name nil
+ truncate-lines t
+ font-lock-defaults '((kubedoc-font-lock-defaults))
+ imenu-generic-expression kubedoc-imenu-generic-expression)
+ (view-mode)
+ (auto-fill-mode -1)
+ (goto-char (point-min))
+ (kubedoc--highlight-field-links)
+ (goto-char (point-min)))
+
+
+(provide 'kubedoc)
+;;; kubedoc.el ends here